Browse Source

migrate from flask to quart.

also remove all flaskiness from static file serving.
and reference all vendored scripts on the base tempĺate for simplicity.
aiosqlite
fiatjaf 5 years ago
parent
commit
f01028eac7
  1. 8
      Makefile
  2. 10
      Pipfile
  3. 223
      Pipfile.lock
  4. 4
      app.json
  5. 8
      docs/devs/installation.md
  6. 60
      lnbits/app.py
  7. 2
      lnbits/commands.py
  8. 6
      lnbits/core/__init__.py
  9. 2
      lnbits/core/crud.py
  10. 2
      lnbits/core/services.py
  11. 7
      lnbits/core/templates/core/extensions.html
  12. 7
      lnbits/core/templates/core/index.html
  13. 20
      lnbits/core/templates/core/wallet.html
  14. 16
      lnbits/core/views/api.py
  15. 22
      lnbits/core/views/generic.py
  16. 4
      lnbits/core/views/lnurl.py
  17. 21
      lnbits/decorators.py
  18. 6
      lnbits/ext.py
  19. 2
      lnbits/extensions/amilk/__init__.py
  20. 11
      lnbits/extensions/amilk/views.py
  21. 10
      lnbits/extensions/amilk/views_api.py
  22. 2
      lnbits/extensions/diagonalley/__init__.py
  23. 10
      lnbits/extensions/diagonalley/views.py
  24. 32
      lnbits/extensions/diagonalley/views_api.py
  25. 2
      lnbits/extensions/events/__init__.py
  26. 1
      lnbits/extensions/events/templates/events/display.html
  27. 17
      lnbits/extensions/events/templates/events/register.html
  28. 1
      lnbits/extensions/events/templates/events/ticket.html
  29. 28
      lnbits/extensions/events/views.py
  30. 20
      lnbits/extensions/events/views_api.py
  31. 2
      lnbits/extensions/example/__init__.py
  32. 6
      lnbits/extensions/example/views.py
  33. 4
      lnbits/extensions/example/views_api.py
  34. 2
      lnbits/extensions/lndhub/__init__.py
  35. 6
      lnbits/extensions/lndhub/decorators.py
  36. 7
      lnbits/extensions/lndhub/templates/lndhub/index.html
  37. 6
      lnbits/extensions/lndhub/views.py
  38. 24
      lnbits/extensions/lndhub/views_api.py
  39. 2
      lnbits/extensions/lnticket/__init__.py
  40. 1
      lnbits/extensions/lnticket/templates/lnticket/display.html
  41. 10
      lnbits/extensions/lnticket/views.py
  42. 16
      lnbits/extensions/lnticket/views_api.py
  43. 2
      lnbits/extensions/lnurlp/__init__.py
  44. 2
      lnbits/extensions/lnurlp/models.py
  45. 1
      lnbits/extensions/lnurlp/templates/lnurlp/display.html
  46. 1
      lnbits/extensions/lnurlp/templates/lnurlp/index.html
  47. 1
      lnbits/extensions/lnurlp/templates/lnurlp/print_qr.html
  48. 16
      lnbits/extensions/lnurlp/views.py
  49. 14
      lnbits/extensions/lnurlp/views_api.py
  50. 2
      lnbits/extensions/paywall/__init__.py
  51. 1
      lnbits/extensions/paywall/templates/paywall/display.html
  52. 11
      lnbits/extensions/paywall/views.py
  53. 12
      lnbits/extensions/paywall/views_api.py
  54. 2
      lnbits/extensions/tpos/__init__.py
  55. 1
      lnbits/extensions/tpos/templates/tpos/tpos.html
  56. 10
      lnbits/extensions/tpos/views.py
  57. 12
      lnbits/extensions/tpos/views_api.py
  58. 2
      lnbits/extensions/usermanager/__init__.py
  59. 9
      lnbits/extensions/usermanager/views.py
  60. 27
      lnbits/extensions/usermanager/views_api.py
  61. 6
      lnbits/extensions/withdraw/__init__.py
  62. 2
      lnbits/extensions/withdraw/models.py
  63. 2
      lnbits/extensions/withdraw/templates/withdraw/display.html
  64. 7
      lnbits/extensions/withdraw/templates/withdraw/index.html
  65. 12
      lnbits/extensions/withdraw/templates/withdraw/print_qr.html
  66. 16
      lnbits/extensions/withdraw/views.py
  67. 16
      lnbits/extensions/withdraw/views_api.py
  68. 3
      lnbits/settings.py
  69. 78
      lnbits/static/css/base.css
  70. 44
      lnbits/templates/base.html
  71. 15
      lnbits/templates/print.html
  72. 44
      requirements.txt

8
Makefile

@ -1,4 +1,4 @@
all: format check all: format check lnbits/static/css/base.css requirements.txt
format: prettier black format: prettier black
@ -18,3 +18,9 @@ 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
lnbits/static/css/base.css: lnbits/static/scss/base.scss
./venv/bin/pyscss -o lnbits/static/css/base.css lnbits/static/scss/base.scss
requirements.txt: Pipfile.lock
cat Pipfile.lock | jq -r '.default | map_values(.version) | to_entries | map("\(.key)\(.value)") | join("\n")' > requirements.txt

10
Pipfile

@ -11,15 +11,15 @@ 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"

223
Pipfile.lock

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "2270f2525e54e976b09491e458033d25ec5bbdea9e74d417e787df33031c6948" "sha256": "2c716474f9f263d8e1310ca44c2f50996f3516273b483a40e2b2ad68b8071dd6"
}, },
"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": [
@ -225,6 +235,13 @@
"markers": "python_version >= '3.5'", "markers": "python_version >= '3.5'",
"version": "==3.7.1" "version": "==3.7.1"
}, },
"priority": {
"hashes": [
"sha256:6bc1961a6d7fcacbfc337769f1a382c8e746566aaa365e78047abe9f66b2ffbe",
"sha256:be4fcb94b5e37cdeb40af5533afe6dd603bd665fe9c8b3052610fc1001d5d1eb"
],
"version": "==1.3.0"
},
"pydantic": { "pydantic": {
"hashes": [ "hashes": [
"sha256:1783c1d927f9e1366e0e0609ae324039b2479a1a282a98ed6a6836c9ed02002c", "sha256:1783c1d927f9e1366e0e0609ae324039b2479a1a282a98ed6a6836c9ed02002c",
@ -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,11 +386,11 @@
}, },
"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": [
@ -353,43 +410,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": { "flake8": {
"hashes": [ "hashes": [
@ -407,14 +464,6 @@
"index": "pypi", "index": "pypi",
"version": "==17.8.0" "version": "==17.8.0"
}, },
"importlib-metadata": {
"hashes": [
"sha256:90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83",
"sha256:dc15b2969b4ce36305c51eebe62d418ac7791e9a157911d58bfb1f9ccd8e2070"
],
"markers": "python_version < '3.8'",
"version": "==1.7.0"
},
"iniconfig": { "iniconfig": {
"hashes": [ "hashes": [
"sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437", "sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437",
@ -521,11 +570,11 @@
}, },
"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-cov": { "pytest-cov": {
"hashes": [ "hashes": [
@ -608,16 +657,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"
} }
} }
} }

4
app.json

@ -1,7 +1,5 @@
{ {
"scripts": { "scripts": {
"dokku": { "dokku": {}
"predeploy": "flask migrate"
}
} }
} }

8
docs/devs/installation.md

@ -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

60
lnbits/app.py

@ -1,27 +1,28 @@
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 from .helpers import get_valid_extensions
secure_headers = SecureHeaders(hsts=False)
def create_app(config_object="lnbits.settings") -> Flask:
"""Create application factory, as explained here: http://flask.pocoo.org/docs/patterns/appfactories/. def create_app(config_object="lnbits.settings") -> Quart:
"""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)
register_blueprints(app) register_blueprints(app)
register_filters(app) register_filters(app)
register_commands(app) register_commands(app)
@ -44,35 +45,11 @@ def register_blueprints(app) -> None:
def register_commands(app): def register_commands(app):
"""Register Click commands.""" """Register Click commands."""
app.cli.add_command(flask_migrate) app.cli.add_command(db_migrate)
def register_flask_extensions(app):
"""Register Flask extensions."""
"""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)
assets.register("base_css", Bundle("scss/base.scss", filters="pyscss", output="css/base.css"))
compress.init_app(app)
def register_filters(app): def register_filters(app):
"""Jinja filters.""" """Jinja filters."""
app.jinja_env.globals["DEBUG"] = app.config["DEBUG"]
app.jinja_env.globals["EXTENSIONS"] = get_valid_extensions() 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"]
@ -81,9 +58,14 @@ def register_request_hooks(app):
"""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)

2
lnbits/commands.py

@ -9,7 +9,7 @@ from .helpers import get_valid_extensions
@click.command("migrate") @click.command("migrate")
def flask_migrate(): def db_migrate():
migrate_databases() migrate_databases()

6
lnbits/core/__init__.py

@ -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
lnbits/core/crud.py

@ -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

2
lnbits/core/services.py

@ -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

7
lnbits/core/templates/core/extensions.html

@ -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="/static/core/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"

7
lnbits/core/templates/core/index.html

@ -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>

20
lnbits/core/templates/core/wallet.html

@ -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>

16
lnbits/core/views/api.py

@ -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:

22
lnbits/core/views/generic.py

@ -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

4
lnbits/core/views/lnurl.py

@ -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:

21
lnbits/decorators.py

@ -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

6
lnbits/ext.py

@ -1,6 +0,0 @@
from flask_assets import Environment # type: ignore
from flask_compress import Compress # type: ignore
assets = Environment()
compress = Compress()

2
lnbits/extensions/amilk/__init__.py

@ -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")

11
lnbits/extensions/amilk/views.py

@ -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)

10
lnbits/extensions/amilk/views_api.py

@ -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:

2
lnbits/extensions/diagonalley/__init__.py

@ -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")

10
lnbits/extensions/diagonalley/views.py

@ -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)

32
lnbits/extensions/diagonalley/views_api.py

@ -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,7 +18,7 @@ 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
@ -30,7 +30,7 @@ from lnbits.db import open_ext_db
@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)

2
lnbits/extensions/events/__init__.py

@ -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")

1
lnbits/extensions/events/templates/events/display.html

@ -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)

17
lnbits/extensions/events/templates/events/register.html

@ -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)

1
lnbits/extensions/events/templates/events/ticket.html

@ -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({

28
lnbits/extensions/events/views.py

@ -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
)

20
lnbits/extensions/events/views_api.py

@ -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)

2
lnbits/extensions/example/__init__.py

@ -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")

6
lnbits/extensions/example/views.py

@ -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)

4
lnbits/extensions/example/views_api.py

@ -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 = [
{ {

2
lnbits/extensions/lndhub/__init__.py

@ -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")

6
lnbits/extensions/lndhub/decorators.py

@ -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

7
lnbits/extensions/lndhub/templates/lndhub/index.html

@ -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 %}

6
lnbits/extensions/lndhub/views.py

@ -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)

24
lnbits/extensions/lndhub/views_api.py

@ -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

2
lnbits/extensions/lnticket/__init__.py

@ -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")

1
lnbits/extensions/lnticket/templates/lnticket/display.html

@ -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)

10
lnbits/extensions/lnticket/views.py

@ -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,

16
lnbits/extensions/lnticket/views_api.py

@ -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:

2
lnbits/extensions/lnurlp/__init__.py

@ -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")

2
lnbits/extensions/lnurlp/models.py

@ -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

1
lnbits/extensions/lnurlp/templates/lnurlp/display.html

@ -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)

1
lnbits/extensions/lnurlp/templates/lnurlp/index.html

@ -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)

1
lnbits/extensions/lnurlp/templates/lnurlp/print_qr.html

@ -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)

16
lnbits/extensions/lnurlp/views.py

@ -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)

14
lnbits/extensions/lnurlp/views_api.py

@ -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

2
lnbits/extensions/paywall/__init__.py

@ -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")

1
lnbits/extensions/paywall/templates/paywall/display.html

@ -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)

11
lnbits/extensions/paywall/views.py

@ -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)

12
lnbits/extensions/paywall/views_api.py

@ -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:

2
lnbits/extensions/tpos/__init__.py

@ -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")

1
lnbits/extensions/tpos/templates/tpos/tpos.html

@ -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)

10
lnbits/extensions/tpos/views.py

@ -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)

12
lnbits/extensions/tpos/views_api.py

@ -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:

2
lnbits/extensions/usermanager/__init__.py

@ -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")

9
lnbits/extensions/usermanager/views.py

@ -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)

27
lnbits/extensions/usermanager/views_api.py

@ -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,12 +17,7 @@ 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
@ -30,7 +25,7 @@ from ...core import update_user_extension
@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:

6
lnbits/extensions/withdraw/__init__.py

@ -1,7 +1,9 @@
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", static_url_path="/static"
)
from .views_api import * # noqa from .views_api import * # noqa

2
lnbits/extensions/withdraw/models.py

@ -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

2
lnbits/extensions/withdraw/templates/withdraw/display.html

@ -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)

7
lnbits/extensions/withdraw/templates/withdraw/index.html

@ -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>

12
lnbits/extensions/withdraw/templates/withdraw/print_qr.html

@ -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)

16
lnbits/extensions/withdraw/views.py

@ -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)

16
lnbits/extensions/withdraw/views_api.py

@ -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)

3
lnbits/settings.py

@ -11,9 +11,6 @@ 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")
DEBUG = ENV == "development"
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"))
LNBITS_ALLOWED_USERS: List[str] = env.list("LNBITS_ALLOWED_USERS", default=[], subcast=str) LNBITS_ALLOWED_USERS: List[str] = env.list("LNBITS_ALLOWED_USERS", default=[], subcast=str)

78
lnbits/static/css/base.css

@ -1,77 +1 @@
[v-cloak] { [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:#f5f5f5}body.body--dark .q-field--error .text-negative,body.body--dark .q-field--error .q-field__messages{color:#ff0 !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}
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;
}

44
lnbits/templates/base.html

@ -5,11 +5,15 @@
<link <link
rel="stylesheet" rel="stylesheet"
type="text/css" type="text/css"
href="{{ url_for('static', filename='vendor/quasar@1.13.2/quasar.min.css') }}" href="/static/vendor/quasar@1.13.2/quasar.min.css"
/> />
{% assets 'base_css' %} <link
<link rel="stylesheet" type="text/css" href="{{ ASSET_URL }}" /> rel="stylesheet"
{% endassets %} {% block styles %}{% endblock %} type="text/css"
href="/static/vendor/vue-qrcode-reader@2.2.0/vue-qrcode-reader.min.css"
/>
<link rel="stylesheet" type="text/css" href="/static/css/base.css" />
{% 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 +112,21 @@
{% 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="/static/vendor/vue@2.6.12/vue.js"></script>
<script src="{{ url_for('static', filename='vendor/vuex@3.5.1/vuex.js') }}"></script> <script src="/static/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="/static/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 src="/static/vendor/quasar@1.13.2/quasar.umd.js"></script>
{% else %} {% assets output='__bundle__/vue.js', <script src="/static/vendor/axios@0.20.0/axios.min.js"></script>
'vendor/quasar@1.13.2/quasar.ie.polyfills.umd.min.js', <script src="/static/vendor/underscore@1.10.2/underscore.min.js"></script>
'vendor/vue@2.6.12/vue.min.js', 'vendor/vue-router@3.4.3/vue-router.min.js', <script src="/static/vendor/vue-qrcode@1.0.2/vue-qrcode.min.js"></script>
'vendor/vuex@3.5.1/vuex.min.js', 'vendor/quasar@1.13.2/quasar.umd.min.js' %} <script src="/static/vendor/moment@2.27.0/moment.min.js"></script>
<script type="text/javascript" src="{{ ASSET_URL }}"></script> <script src="/static/vendor/chart.js@2.9.3/chart.min.js"></script>
{% endassets %} {% endif %} {% assets filters='rjsmin', <script src="/static/vendor/bolt11/utils.js"></script>
output='__bundle__/base.js', 'vendor/axios@0.20.0/axios.min.js', <script src="/static/vendor/bolt11/decoder.js"></script>
'vendor/underscore@1.10.2/underscore.min.js', 'js/base.js', <script src="/static/vendor/vue-qrcode-reader@2.2.0/vue-qrcode-reader.min.js"></script>
'js/components.js' %} <script src="/static/js/base.js"></script>
<script type="text/javascript" src="{{ ASSET_URL }}"></script> <script src="/static/js/components.js"></script>
{% endassets %} {% block scripts %}{% endblock %} {% block scripts %}{% endblock %}
</body> </body>
</html> </html>

15
lnbits/templates/print.html

@ -38,13 +38,12 @@
</q-page-container> </q-page-container>
</q-layout> </q-layout>
{% if DEBUG %} <script src="/static/vendor/quasar@1.13.2/quasar.ie.polyfills.umd.min.js"></script>
<script src="{{ url_for('static', filename='vendor/vue@2.6.12/vue.js') }}"></script> <script src="/static/vendor/vue@2.6.12/vue.min.js"></script>
<script src="{{ url_for('static', filename='vendor/quasar@1.13.2/quasar.umd.js') }}"></script> <script src="/static/vendor/vuex@3.5.1/vuex.js"></script>
{% else %} {% assets output='__bundle__/vue-print.js', <script src="/static/vendor/vue-router@3.4.3/vue-router.js"></script>
'vendor/quasar@1.13.2/quasar.ie.polyfills.umd.min.js', <script src="/static/vendor/quasar@1.13.2/quasar.umd.min.js"></script>
'vendor/vue@2.6.12/vue.min.js', 'vendor/quasar@1.13.2/quasar.umd.min.js' %} <script src="/static/vendor/vue-qrcode@1.0.2/vue-qrcode.min.js"></script>
<script type="text/javascript" src="{{ ASSET_URL }}"></script> {% block scripts %}{% endblock %}
{% endassets %} {% endif %} {% block scripts %}{% endblock %}
</body> </body>
</html> </html>

44
requirements.txt

@ -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.7.1
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

Loading…
Cancel
Save