diff --git a/.env.example b/.env.example index a43bfca..b6e88e4 100644 --- a/.env.example +++ b/.env.example @@ -1,12 +1,16 @@ -FLASK_APP=lnbits.app -FLASK_ENV=development +QUART_APP=lnbits.app +QUART_ENV=development +QUART_DEBUG=true + +HOST=127.0.0.1 +PORT=5000 LNBITS_SITE_TITLE=LNbits LNBITS_ALLOWED_USERS="" LNBITS_DEFAULT_WALLET_NAME="LNbits wallet" LNBITS_DATA_FOLDER="/your_custom_data_folder" #IMPORTANT! i.e. "/home/satoshi/lnbits/lnbits/data" LNBITS_DISABLED_EXTENSIONS="amilk" -LNBITS_FORCE_HTTPS=1 +LNBITS_FORCE_HTTPS=true LNBITS_SERVICE_FEE="0.0" # Choose from LNPayWallet, OpenNodeWallet, LntxbotWallet, LndWallet (gRPC), LndRestWallet, CLightningWallet, LnbitsWallet diff --git a/.gitignore b/.gitignore index 08b009f..ca3fcd0 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,4 @@ venv __bundle__ node_modules +lnbits/static/bundle.* diff --git a/Dockerfile b/Dockerfile index 9e53a61..f959cbd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ FROM python:3.7-slim WORKDIR /app COPY requirements.txt /app/ RUN pip install --no-cache-dir -q -r requirements.txt -RUN pip install --no-cache-dir -q gunicorn gevent +RUN pip install --no-cache-dir -q hypercorn COPY . /app EXPOSE 5000 diff --git a/Makefile b/Makefile index a95fde2..f80747a 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -all: format check +all: format check requirements.txt format: prettier black @@ -18,3 +18,6 @@ checkprettier: $(shell find lnbits -name "*.js" -name ".html") checkblack: $(shell find lnbits -name "*.py") ./venv/bin/black --check lnbits + +requirements.txt: Pipfile.lock + cat Pipfile.lock | jq -r '.default | map_values(.version) | to_entries | map("\(.key)\(.value)") | join("\n")' > requirements.txt diff --git a/Pipfile b/Pipfile index 0962b5d..0ae2523 100644 --- a/Pipfile +++ b/Pipfile @@ -11,19 +11,19 @@ bitstring = "*" cerberus = "*" ecdsa = "*" environs = "*" -flask = "*" -flask-assets = "*" -flask-compress = "*" -flask-cors = "*" -flask-talisman = "*" lnurl = "*" pyscss = "*" requests = "*" shortuuid = "*" +quart = "*" +quart-cors = "*" +quart-compress = "*" +secure = "*" +typing-extensions = "*" [dev-packages] black = "==20.8b1" -flake8 = "*" -flake8-mypy = "*" pytest = "*" pytest-cov = "*" +pytest-asyncio = "*" +mypy = "==0.761" diff --git a/Pipfile.lock b/Pipfile.lock index f2e187a..8101e85 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "2270f2525e54e976b09491e458033d25ec5bbdea9e74d417e787df33031c6948" + "sha256": "775e2ea809508c83df4696bba3a38a03e288cba22f0a7f562120230f40351ab9" }, "pipfile-spec": 6, "requires": { @@ -16,6 +16,13 @@ ] }, "default": { + "aiofiles": { + "hashes": [ + "sha256:377fdf7815cc611870c59cbd07b68b180841d2a2b79812d8c218be02448c2acb", + "sha256:98e6bcfd1b50f97db4980e182ddd509b7cc35909e903a8fe50d8849e02d815af" + ], + "version": "==0.5.0" + }, "bech32": { "hashes": [ "sha256:7d6db8214603bd7871fcfa6c0826ef68b85b0abd90fa21c285a9c5e21d2bd899", @@ -31,6 +38,12 @@ "index": "pypi", "version": "==3.1.7" }, + "blinker": { + "hashes": [ + "sha256:471aee25f3992bd325afa3772f1063dbdbbca947a041b8b89466dc00d606f8b6" + ], + "version": "==1.4" + }, "brotli": { "hashes": [ "sha256:160c78292e98d21e73a4cc7f76a234390e516afcd982fa17e1422f7c6a9ce9c8", @@ -108,44 +121,41 @@ "index": "pypi", "version": "==8.0.0" }, - "flask": { + "h11": { "hashes": [ - "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060", - "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557" + "sha256:311dc5478c2568cc07262e0381cdfc5b9c6ba19775905736c87e81ae6662b9fd", + "sha256:9eecfbafc980976dbff26a01dd3487644dd5d00f8038584451fc64a660f7c502" ], - "index": "pypi", - "version": "==1.1.2" + "version": "==0.10.0" }, - "flask-assets": { + "h2": { "hashes": [ - "sha256:1dfdea35e40744d46aada72831f7613d67bf38e8b20ccaaa9e91fdc37aa3b8c2", - "sha256:2845bd3b479be9db8556801e7ebc2746ce2d9edb4e7b64a1c786ecbfc1e5867b" + "sha256:61e0f6601fa709f35cdb730863b4e5ec7ad449792add80d1410d4174ed139af5", + "sha256:875f41ebd6f2c44781259005b157faed1a5031df3ae5aa7bcb4628a6c0782f14" ], - "index": "pypi", - "version": "==2.0" + "version": "==3.2.0" }, - "flask-compress": { + "hpack": { "hashes": [ - "sha256:f367b2b46003dd62be34f7fb1379938032656dca56377a9bc90e7188e4289a7c" + "sha256:0edd79eda27a53ba5be2dfabf3b15780928a0dff6eb0c60a3d6767720e970c89", + "sha256:8eec9c1f4bfae3408a3f30500261f7e6a65912dc138526ea054f9ad98892e9d2" ], - "index": "pypi", - "version": "==1.5.0" + "version": "==3.0.0" }, - "flask-cors": { + "hypercorn": { "hashes": [ - "sha256:6bcfc100288c5d1bcb1dbb854babd59beee622ffd321e444b05f24d6d58466b8", - "sha256:cee4480aaee421ed029eaa788f4049e3e26d15b5affb6a880dade6bafad38324" + "sha256:19f32e7267225c8108ad585b2c5deddf1fe75950797a0e87a682a3a00ef1af95", + "sha256:809d77f3bf9fa0794a598d8dfa0f8d889e7e1c2f927581cd33068803169dc474" ], - "index": "pypi", - "version": "==3.0.9" + "markers": "python_version >= '3.7'", + "version": "==0.10.2" }, - "flask-talisman": { + "hyperframe": { "hashes": [ - "sha256:468131464a249274ed226efc21b372518f442487e58918ccab8357eaa638fd1f", - "sha256:eaa754f4b771dfbe473843391d69643b79e3a38c865790011ac5e4179c68e3ec" + "sha256:5187962cb16dcc078f23cb5a4b110098d546c3f41ff2d4038a9896893bbd0b40", + "sha256:a9f5c17f2cc3c719b917c4f33ed1c61bd1f8dfac4b1bd23b7c80b3400971b41f" ], - "index": "pypi", - "version": "==0.7.0" + "version": "==5.2.0" }, "idna": { "hashes": [ @@ -219,11 +229,18 @@ }, "marshmallow": { "hashes": [ - "sha256:67bf4cae9d3275b3fc74bd7ff88a7c98ee8c57c94b251a67b031dc293ecc4b76", - "sha256:a2a5eefb4b75a3b43f05be1cca0b6686adf56af7465c3ca629e5ad8d1e1fe13d" + "sha256:2272273505f1644580fbc66c6b220cc78f893eb31f1ecde2af98ad28011e9811", + "sha256:47911dd7c641a27160f0df5fd0fe94667160ffe97f70a42c3cc18388d86098cc" ], "markers": "python_version >= '3.5'", - "version": "==3.7.1" + "version": "==3.8.0" + }, + "priority": { + "hashes": [ + "sha256:6bc1961a6d7fcacbfc337769f1a382c8e746566aaa365e78047abe9f66b2ffbe", + "sha256:be4fcb94b5e37cdeb40af5533afe6dd603bd665fe9c8b3052610fc1001d5d1eb" + ], + "version": "==1.3.0" }, "pydantic": { "hashes": [ @@ -262,6 +279,30 @@ ], "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": { "hashes": [ "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b", @@ -270,6 +311,14 @@ "index": "pypi", "version": "==2.24.0" }, + "secure": { + "hashes": [ + "sha256:4dc8dd4b548831c3ad7f94079332c41d67c781eccc32215ff5a8a49582c1a447", + "sha256:b3bf1e39ebf40040fc3248392343a5052aa14cb45fc87ec91b0bd11f19cc46bd" + ], + "index": "pypi", + "version": "==0.2.1" + }, "shortuuid": { "hashes": [ "sha256:3c11d2007b915c43bee3e10625f068d8a349e04f0d81f08f5fa08507427ebf1f", @@ -286,13 +335,20 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.15.0" }, + "toml": { + "hashes": [ + "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f", + "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88" + ], + "version": "==0.10.1" + }, "typing-extensions": { "hashes": [ "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918", "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c", "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f" ], - "markers": "python_version < '3.8'", + "index": "pypi", "version": "==3.7.4.3" }, "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'", "version": "==1.25.10" }, - "webassets": { - "hashes": [ - "sha256:167132337677c8cedc9705090f6d48da3fb262c8e0b2773b29f3352f050181cd", - "sha256:a31a55147752ba1b3dc07dee0ad8c8efff274464e08bbdb88c1fd59ffd552724" - ], - "version": "==2.0" - }, "werkzeug": { "hashes": [ "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'", "version": "==1.0.1" + }, + "wsproto": { + "hashes": [ + "sha256:614798c30e5dc2b3f65acc03d2d50842b97621487350ce79a80a711229edfa9d", + "sha256:e3d190a11d9307112ba23bbe60055604949b172143969c8f641318476a9b6f1d" + ], + "markers": "python_full_version >= '3.6.1'", + "version": "==0.15.0" } }, "develop": { @@ -329,16 +386,15 @@ }, "attrs": { "hashes": [ - "sha256:0ef97238856430dcf9228e07f316aefc17e8939fc8507e18c6501b761ef1a42a", - "sha256:2867b7b9f8326499ab5b0e2d12801fa5c98842d2cbd22b35112ae04bf85b4dff" + "sha256:26b54ddbbb9ee1d34d5d3668dd37d6cf74990ab23c828c2888dccdceee395594", + "sha256:fce7fc47dfc976152e82d53ff92fa0407700c21acd20886a13777a0d20e655dc" ], "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": { "hashes": [ - "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea", - "sha256:70b62ef1527c950db59062cda342ea224d772abdf6adc58b86a45421bab20a6b" + "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea" ], "index": "pypi", "version": "==20.8b1" @@ -353,67 +409,43 @@ }, "coverage": { "hashes": [ - "sha256:098a703d913be6fbd146a8c50cc76513d726b022d170e5e98dc56d958fd592fb", - "sha256:16042dc7f8e632e0dcd5206a5095ebd18cb1d005f4c89694f7f8aafd96dd43a3", - "sha256:1adb6be0dcef0cf9434619d3b892772fdb48e793300f9d762e480e043bd8e716", - "sha256:27ca5a2bc04d68f0776f2cdcb8bbd508bbe430a7bf9c02315cd05fb1d86d0034", - "sha256:28f42dc5172ebdc32622a2c3f7ead1b836cdbf253569ae5673f499e35db0bac3", - "sha256:2fcc8b58953d74d199a1a4d633df8146f0ac36c4e720b4a1997e9b6327af43a8", - "sha256:304fbe451698373dc6653772c72c5d5e883a4aadaf20343592a7abb2e643dae0", - "sha256:30bc103587e0d3df9e52cd9da1dd915265a22fad0b72afe54daf840c984b564f", - "sha256:40f70f81be4d34f8d491e55936904db5c527b0711b2a46513641a5729783c2e4", - "sha256:4186fc95c9febeab5681bc3248553d5ec8c2999b8424d4fc3a39c9cba5796962", - "sha256:46794c815e56f1431c66d81943fa90721bb858375fb36e5903697d5eef88627d", - "sha256:4869ab1c1ed33953bb2433ce7b894a28d724b7aa76c19b11e2878034a4e4680b", - "sha256:4f6428b55d2916a69f8d6453e48a505c07b2245653b0aa9f0dee38785939f5e4", - "sha256:52f185ffd3291196dc1aae506b42e178a592b0b60a8610b108e6ad892cfc1bb3", - "sha256:538f2fd5eb64366f37c97fdb3077d665fa946d2b6d95447622292f38407f9258", - "sha256:64c4f340338c68c463f1b56e3f2f0423f7b17ba6c3febae80b81f0e093077f59", - "sha256:675192fca634f0df69af3493a48224f211f8db4e84452b08d5fcebb9167adb01", - "sha256:700997b77cfab016533b3e7dbc03b71d33ee4df1d79f2463a318ca0263fc29dd", - "sha256:8505e614c983834239f865da2dd336dcf9d72776b951d5dfa5ac36b987726e1b", - "sha256:962c44070c281d86398aeb8f64e1bf37816a4dfc6f4c0f114756b14fc575621d", - "sha256:9e536783a5acee79a9b308be97d3952b662748c4037b6a24cbb339dc7ed8eb89", - "sha256:9ea749fd447ce7fb1ac71f7616371f04054d969d412d37611716721931e36efd", - "sha256:a34cb28e0747ea15e82d13e14de606747e9e484fb28d63c999483f5d5188e89b", - "sha256:a3ee9c793ffefe2944d3a2bd928a0e436cd0ac2d9e3723152d6fd5398838ce7d", - "sha256:aab75d99f3f2874733946a7648ce87a50019eb90baef931698f96b76b6769a46", - "sha256:b1ed2bdb27b4c9fc87058a1cb751c4df8752002143ed393899edb82b131e0546", - "sha256:b360d8fd88d2bad01cb953d81fd2edd4be539df7bfec41e8753fe9f4456a5082", - "sha256:b8f58c7db64d8f27078cbf2a4391af6aa4e4767cc08b37555c4ae064b8558d9b", - "sha256:c1bbb628ed5192124889b51204de27c575b3ffc05a5a91307e7640eff1d48da4", - "sha256:c2ff24df02a125b7b346c4c9078c8936da06964cc2d276292c357d64378158f8", - "sha256:c890728a93fffd0407d7d37c1e6083ff3f9f211c83b4316fae3778417eab9811", - "sha256:c96472b8ca5dc135fb0aa62f79b033f02aa434fb03a8b190600a5ae4102df1fd", - "sha256:ce7866f29d3025b5b34c2e944e66ebef0d92e4a4f2463f7266daa03a1332a651", - "sha256:e26c993bd4b220429d4ec8c1468eca445a4064a61c74ca08da7429af9bc53bb0" + "sha256:0203acd33d2298e19b57451ebb0bed0ab0c602e5cf5a818591b4918b1f97d516", + "sha256:0f313707cdecd5cd3e217fc68c78a960b616604b559e9ea60cc16795c4304259", + "sha256:1c6703094c81fa55b816f5ae542c6ffc625fec769f22b053adb42ad712d086c9", + "sha256:1d44bb3a652fed01f1f2c10d5477956116e9b391320c94d36c6bf13b088a1097", + "sha256:280baa8ec489c4f542f8940f9c4c2181f0306a8ee1a54eceba071a449fb870a0", + "sha256:29a6272fec10623fcbe158fdf9abc7a5fa032048ac1d8631f14b50fbfc10d17f", + "sha256:2b31f46bf7b31e6aa690d4c7a3d51bb262438c6dcb0d528adde446531d0d3bb7", + "sha256:2d43af2be93ffbad25dd959899b5b809618a496926146ce98ee0b23683f8c51c", + "sha256:381ead10b9b9af5f64646cd27107fb27b614ee7040bb1226f9c07ba96625cbb5", + "sha256:47a11bdbd8ada9b7ee628596f9d97fbd3851bd9999d398e9436bd67376dbece7", + "sha256:4d6a42744139a7fa5b46a264874a781e8694bb32f1d76d8137b68138686f1729", + "sha256:50691e744714856f03a86df3e2bff847c2acede4c191f9a1da38f088df342978", + "sha256:530cc8aaf11cc2ac7430f3614b04645662ef20c348dce4167c22d99bec3480e9", + "sha256:582ddfbe712025448206a5bc45855d16c2e491c2dd102ee9a2841418ac1c629f", + "sha256:63808c30b41f3bbf65e29f7280bf793c79f54fb807057de7e5238ffc7cc4d7b9", + "sha256:71b69bd716698fa62cd97137d6f2fdf49f534decb23a2c6fc80813e8b7be6822", + "sha256:7858847f2d84bf6e64c7f66498e851c54de8ea06a6f96a32a1d192d846734418", + "sha256:78e93cc3571fd928a39c0b26767c986188a4118edc67bc0695bc7a284da22e82", + "sha256:7f43286f13d91a34fadf61ae252a51a130223c52bfefb50310d5b2deb062cf0f", + "sha256:86e9f8cd4b0cdd57b4ae71a9c186717daa4c5a99f3238a8723f416256e0b064d", + "sha256:8f264ba2701b8c9f815b272ad568d555ef98dfe1576802ab3149c3629a9f2221", + "sha256:9342dd70a1e151684727c9c91ea003b2fb33523bf19385d4554f7897ca0141d4", + "sha256:9361de40701666b034c59ad9e317bae95c973b9ff92513dd0eced11c6adf2e21", + "sha256:9669179786254a2e7e57f0ecf224e978471491d660aaca833f845b72a2df3709", + "sha256:aac1ba0a253e17889550ddb1b60a2063f7474155465577caa2a3b131224cfd54", + "sha256:aef72eae10b5e3116bac6957de1df4d75909fc76d1499a53fb6387434b6bcd8d", + "sha256:bd3166bb3b111e76a4f8e2980fa1addf2920a4ca9b2b8ca36a3bc3dedc618270", + "sha256:c1b78fb9700fc961f53386ad2fd86d87091e06ede5d118b8a50dea285a071c24", + "sha256:c3888a051226e676e383de03bf49eb633cd39fc829516e5334e69b8d81aae751", + "sha256:c5f17ad25d2c1286436761b462e22b5020d83316f8e8fcb5deb2b3151f8f1d3a", + "sha256:c851b35fc078389bc16b915a0a7c1d5923e12e2c5aeec58c52f4aa8085ac8237", + "sha256:cb7df71de0af56000115eafd000b867d1261f786b5eebd88a0ca6360cccfaca7", + "sha256:cedb2f9e1f990918ea061f28a0f0077a07702e3819602d3507e2ff98c8d20636", + "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'", - "version": "==5.2.1" - }, - "flake8": { - "hashes": [ - "sha256:15e351d19611c887e482fb960eae4d44845013cc142d42896e9862f775d8cf5c", - "sha256:f04b9fcbac03b0a3e58c0ab3a0ecc462e023a9faf046d57794184028123aa208" - ], - "index": "pypi", - "version": "==3.8.3" - }, - "flake8-mypy": { - "hashes": [ - "sha256:47120db63aff631ee1f84bac6fe8e64731dc66da3efc1c51f85e15ade4a3ba18", - "sha256:cff009f4250e8391bf48990093cff85802778c345c8449d6498b62efefeebcbc" - ], - "index": "pypi", - "version": "==17.8.0" - }, - "importlib-metadata": { - "hashes": [ - "sha256:90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83", - "sha256:dc15b2969b4ce36305c51eebe62d418ac7791e9a157911d58bfb1f9ccd8e2070" - ], - "markers": "python_version < '3.8'", - "version": "==1.7.0" + "version": "==5.3" }, "iniconfig": { "hashes": [ @@ -422,13 +454,6 @@ ], "version": "==1.0.1" }, - "mccabe": { - "hashes": [ - "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", - "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" - ], - "version": "==0.6.1" - }, "more-itertools": { "hashes": [ "sha256:6f83822ae94818eae2612063a5101a7311e68ae8002005b5e05f03fd74a86a20", @@ -439,23 +464,23 @@ }, "mypy": { "hashes": [ - "sha256:2c6cde8aa3426c1682d35190b59b71f661237d74b053822ea3d748e2c9578a7c", - "sha256:3fdda71c067d3ddfb21da4b80e2686b71e9e5c72cca65fa216d207a358827f86", - "sha256:5dd13ff1f2a97f94540fd37a49e5d255950ebcdf446fb597463a40d0df3fac8b", - "sha256:6731603dfe0ce4352c555c6284c6db0dc935b685e9ce2e4cf220abe1e14386fd", - "sha256:6bb93479caa6619d21d6e7160c552c1193f6952f0668cdda2f851156e85186fc", - "sha256:81c7908b94239c4010e16642c9102bfc958ab14e36048fa77d0be3289dda76ea", - "sha256:9c7a9a7ceb2871ba4bac1cf7217a7dd9ccd44c27c2950edbc6dc08530f32ad4e", - "sha256:a4a2cbcfc4cbf45cd126f531dedda8485671545b43107ded25ce952aac6fb308", - "sha256:b7fbfabdbcc78c4f6fc4712544b9b0d6bf171069c6e0e3cb82440dd10ced3406", - "sha256:c05b9e4fb1d8a41d41dec8786c94f3b95d3c5f528298d769eb8e73d293abc48d", - "sha256:d7df6eddb6054d21ca4d3c6249cae5578cb4602951fd2b6ee2f5510ffb098707", - "sha256:e0b61738ab504e656d1fe4ff0c0601387a5489ca122d55390ade31f9ca0e252d", - "sha256:eff7d4a85e9eea55afa34888dfeaccde99e7520b51f867ac28a48492c0b1130c", - "sha256:f05644db6779387ccdb468cc47a44b4356fc2ffa9287135d05b70a98dc83b89a" + "sha256:0a9a45157e532da06fe56adcfef8a74629566b607fa2c1ac0122d1ff995c748a", + "sha256:2c35cae79ceb20d47facfad51f952df16c2ae9f45db6cb38405a3da1cf8fc0a7", + "sha256:4b9365ade157794cef9685791032521233729cb00ce76b0ddc78749abea463d2", + "sha256:53ea810ae3f83f9c9b452582261ea859828a9ed666f2e1ca840300b69322c474", + "sha256:634aef60b4ff0f650d3e59d4374626ca6153fcaff96ec075b215b568e6ee3cb0", + "sha256:7e396ce53cacd5596ff6d191b47ab0ea18f8e0ec04e15d69728d530e86d4c217", + "sha256:7eadc91af8270455e0d73565b8964da1642fe226665dd5c9560067cd64d56749", + "sha256:7f672d02fffcbace4db2b05369142e0506cdcde20cea0e07c7c2171c4fd11dd6", + "sha256:85baab8d74ec601e86134afe2bcccd87820f79d2f8d5798c889507d1088287bf", + "sha256:87c556fb85d709dacd4b4cb6167eecc5bbb4f0a9864b69136a0d4640fdc76a36", + "sha256:a6bd44efee4dc8c3324c13785a9dc3519b3ee3a92cada42d2b57762b7053b49b", + "sha256:c6d27bd20c3ba60d5b02f20bd28e20091d6286a699174dfad515636cb09b5a72", + "sha256:e2bb577d10d09a2d8822a042a23b8d62bc3b269667c9eb8e60a6edfa000211b1", + "sha256:f97a605d7c8bc2c6d1172c2f0d5a65b24142e11a58de689046e62c2d632ca8c1" ], - "markers": "python_version >= '3.5'", - "version": "==0.782" + "index": "pypi", + "version": "==0.761" }, "mypy-extensions": { "hashes": [ @@ -495,22 +520,6 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.9.0" }, - "pycodestyle": { - "hashes": [ - "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367", - "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.6.0" - }, - "pyflakes": { - "hashes": [ - "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92", - "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.2.0" - }, "pyparsing": { "hashes": [ "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", @@ -521,11 +530,19 @@ }, "pytest": { "hashes": [ - "sha256:85228d75db9f45e06e57ef9bf4429267f81ac7c0d742cc9ed63d09886a9fe6f4", - "sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad" + "sha256:0e37f61339c4578776e090c3b8f6b16ce4db333889d65d0efb305243ec544b40", + "sha256:c8f57c2a30983f469bf03e68cdfa74dc474ce56b8f280ddcb080dfd91df01043" ], "index": "pypi", - "version": "==6.0.1" + "version": "==6.0.2" + }, + "pytest-asyncio": { + "hashes": [ + "sha256:2eae1e34f6c68fc0a9dc12d4bea190483843ff4708d24277c41568d6b6044f1d", + "sha256:9882c0c6b24429449f5f969a5158b528f39bde47dc32e85b9f0403965017e700" + ], + "index": "pypi", + "version": "==0.14.0" }, "pytest-cov": { "hashes": [ @@ -608,16 +625,8 @@ "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c", "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f" ], - "markers": "python_version < '3.8'", + "index": "pypi", "version": "==3.7.4.3" - }, - "zipp": { - "hashes": [ - "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b", - "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96" - ], - "markers": "python_version >= '3.6'", - "version": "==3.1.0" } } } diff --git a/Procfile b/Procfile index 3d7b459..0b1764f 100644 --- a/Procfile +++ b/Procfile @@ -1 +1 @@ -web: gunicorn -b :5000 lnbits:app -k gevent +web: hypercorn --bind 0.0.0.0:5000 lnbits:app diff --git a/app.json b/app.json index 96a3f11..a08f9f3 100644 --- a/app.json +++ b/app.json @@ -1,7 +1,5 @@ { "scripts": { - "dokku": { - "predeploy": "flask migrate" - } + "dokku": {} } } diff --git a/docs/devs/installation.md b/docs/devs/installation.md index c4aad25..da53cc3 100644 --- a/docs/devs/installation.md +++ b/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 ------------------ -LNbits uses [Flask][flask] as an application server. +LNbits uses [Quart][quart] as an application server. ```sh $ 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 -------- The frontend uses [Vue.js and Quasar][quasar]. -[flask]: http://flask.pocoo.org/ +[quart]: https://pgjones.gitlab.io/ [pipenv]: https://pipenv.pypa.io/ [polar]: https://lightningpolar.com/ [quasar]: https://quasar.dev/start/how-to-use-vue diff --git a/lnbits/__main__.py b/lnbits/__main__.py index 31a4de1..6932fba 100644 --- a/lnbits/__main__.py +++ b/lnbits/__main__.py @@ -1,8 +1,9 @@ from .app import create_app -from .commands import migrate_databases - +from .commands import migrate_databases, transpile_scss, bundle_vendored migrate_databases() +transpile_scss() +bundle_vendored() app = create_app() -app.run() +app.run(host=app.config["HOST"], port=app.config["PORT"]) diff --git a/lnbits/app.py b/lnbits/app.py index bf866b5..4a8c1cc 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -1,27 +1,31 @@ import importlib -from flask import Flask, g -from flask_assets import Bundle # type: ignore -from flask_cors import CORS # type: ignore -from flask_talisman import Talisman # type: ignore -from werkzeug.middleware.proxy_fix import ProxyFix +from quart import Quart, g +from quart_cors import cors # type: ignore +from quart_compress import Compress # type: ignore +from secure import SecureHeaders # type: ignore -from .commands import flask_migrate +from .commands import db_migrate from .core import core_app from .db import open_db -from .ext import assets, compress -from .helpers import get_valid_extensions +from .helpers import get_valid_extensions, get_js_vendored, get_css_vendored, url_for_vendored +from .proxy_fix import ProxyFix +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. """ - app = Flask(__name__, static_folder="static") - app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1, x_host=1) # type: ignore + app = Quart(__name__, static_folder="static") app.config.from_object(config_object) - register_flask_extensions(app) + cors(app) + Compress(app) + ProxyFix(app, x_proto=1, x_host=1) + + register_assets(app) register_blueprints(app) register_filters(app) register_commands(app) @@ -30,7 +34,7 @@ def create_app(config_object="lnbits.settings") -> Flask: return app -def register_blueprints(app) -> None: +def register_blueprints(app: Quart) -> None: """Register Flask blueprints / LNbits extensions.""" app.register_blueprint(core_app) @@ -42,48 +46,42 @@ def register_blueprints(app) -> None: raise ImportError(f"Please make sure that the extension `{ext.code}` follows conventions.") -def register_commands(app): +def register_commands(app: Quart): """Register Click commands.""" - app.cli.add_command(flask_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): + app.cli.add_command(db_migrate) + + +def register_assets(app: Quart): + """Serve each vendored asset separately or a bundle.""" + + @app.before_request + async def vendored_assets_variable(): + if app.config["DEBUG"]: + g.VENDORED_JS = map(url_for_vendored, get_js_vendored()) + g.VENDORED_CSS = map(url_for_vendored, get_css_vendored()) + else: + g.VENDORED_JS = ["/static/bundle.js"] + g.VENDORED_CSS = ["/static/bundle.css"] + + +def register_filters(app: Quart): """Jinja filters.""" - app.jinja_env.globals["DEBUG"] = app.config["DEBUG"] - app.jinja_env.globals["EXTENSIONS"] = get_valid_extensions() app.jinja_env.globals["SITE_TITLE"] = app.config["LNBITS_SITE_TITLE"] + app.jinja_env.globals["EXTENSIONS"] = get_valid_extensions() -def register_request_hooks(app): +def register_request_hooks(app: Quart): """Open the core db for each request so everything happens in a big transaction""" @app.before_request - def before_request(): + async def before_request(): g.db = open_db() + @app.after_request + async def set_secure_headers(response): + secure_headers.quart(response) + return response + @app.teardown_request - def after_request(exc): + async def after_request(exc): g.db.__exit__(type(exc), exc, None) diff --git a/lnbits/commands.py b/lnbits/commands.py index b6be6f3..653175f 100644 --- a/lnbits/commands.py +++ b/lnbits/commands.py @@ -1,18 +1,47 @@ import click import importlib import re +import os import sqlite3 +from scss.compiler import compile_string # type: ignore + from .core import migrations as core_migrations from .db import open_db, open_ext_db -from .helpers import get_valid_extensions +from .helpers import get_valid_extensions, get_css_vendored, get_js_vendored, url_for_vendored +from .settings import LNBITS_PATH @click.command("migrate") -def flask_migrate(): +def db_migrate(): migrate_databases() +@click.command("assets") +def handle_assets(): + transpile_scss() + bundle_vendored() + + +def transpile_scss(): + with open(os.path.join(LNBITS_PATH, "static/scss/base.scss")) as scss: + with open(os.path.join(LNBITS_PATH, "static/css/base.css"), "w") as css: + css.write(compile_string(scss.read())) + + +def bundle_vendored(): + for getfiles, outputpath in [ + (get_js_vendored, os.path.join(LNBITS_PATH, "static/bundle.js")), + (get_css_vendored, os.path.join(LNBITS_PATH, "static/bundle.css")), + ]: + output = "" + for path in getfiles(): + with open(path) as f: + output += "/* " + url_for_vendored(path) + " */\n" + f.read() + ";\n" + with open(outputpath, "w") as f: + f.write(output) + + def migrate_databases(): """Creates the necessary databases if they don't exist already; or migrates them.""" diff --git a/lnbits/core/__init__.py b/lnbits/core/__init__.py index 5af72f9..e35d66d 100644 --- a/lnbits/core/__init__.py +++ b/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 diff --git a/lnbits/core/crud.py b/lnbits/core/crud.py index 35f1019..6d19c90 100644 --- a/lnbits/core/crud.py +++ b/lnbits/core/crud.py @@ -2,7 +2,7 @@ import json import datetime from uuid import uuid4 from typing import List, Optional, Dict -from flask import g +from quart import g from lnbits import bolt11 from lnbits.settings import DEFAULT_WALLET_NAME diff --git a/lnbits/core/services.py b/lnbits/core/services.py index fd42aa6..e16b2f2 100644 --- a/lnbits/core/services.py +++ b/lnbits/core/services.py @@ -1,5 +1,5 @@ from typing import Optional, Tuple, Dict -from flask import g +from quart import g try: from typing import TypedDict # type: ignore diff --git a/lnbits/core/templates/core/extensions.html b/lnbits/core/templates/core/extensions.html index 8c63c49..97fa893 100644 --- a/lnbits/core/templates/core/extensions.html +++ b/lnbits/core/templates/core/extensions.html @@ -1,8 +1,7 @@ {% extends "base.html" %} {% from "macros.jinja" import window_vars with context -%} {% block scripts %} {{ window_vars(user) }} {% assets filters='rjsmin', -output='__bundle__/core/extensions.js', 'core/js/extensions.js' %} - -{% endassets %} {% endblock %} {% block page %} +%} {% block scripts %} {{ window_vars(user) }} + +{% endblock %} {% block page %}