Merge pull request #351 from arcbtc/FastAPI

Latest from arcbtc branch
This commit is contained in:
Arc
2021-10-02 19:12:42 +02:00
committed by GitHub
16 changed files with 382 additions and 258 deletions

View File

@@ -27,6 +27,7 @@ asyncio = "*"
fastapi = "*" fastapi = "*"
uvicorn = {extras = ["standard"], version = "*"} uvicorn = {extras = ["standard"], version = "*"}
sse-starlette = "*" sse-starlette = "*"
jinja2 = "3.0.1"
[dev-packages] [dev-packages]
black = "==20.8b1" black = "==20.8b1"

182
Pipfile.lock generated
View File

@@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "e26f678c4b89a86400e0a62396d06e360bfdf1e0f922d474ded200ee1ffde5c4" "sha256": "97473b3cb250742ebabd8c3a71d4e4c42f8feeaff49dd4542cae24429f096535"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@@ -26,11 +26,11 @@
}, },
"anyio": { "anyio": {
"hashes": [ "hashes": [
"sha256:929a6852074397afe1d989002aa96d457e3e1e5441357c60d03e7eea0e65e1b0", "sha256:85913b4e2fec030e8c72a8f9f98092eeb9e25847a6e00d567751b77e34f856fe",
"sha256:ae57a67583e5ff8b4af47666ff5651c3732d45fd26c929253748e796af860374" "sha256:d7c604dd491eca70e19c78664d685d5e4337612d574419d503e76f5d7d1590bd"
], ],
"markers": "python_full_version >= '3.6.2'", "markers": "python_full_version >= '3.6.2'",
"version": "==3.3.0" "version": "==3.3.1"
}, },
"asgiref": { "asgiref": {
"hashes": [ "hashes": [
@@ -91,11 +91,11 @@
}, },
"charset-normalizer": { "charset-normalizer": {
"hashes": [ "hashes": [
"sha256:0c8911edd15d19223366a194a513099a302055a962bca2cec0f54b8b63175d8b", "sha256:5d209c0a931f215cee683b6445e2d77677e7e75e159f78def0db09d68fafcaa6",
"sha256:f23667ebe1084be45f6ae0538e4a5a865206544097e4e8bbcacf42cd02a348f3" "sha256:5ec46d183433dcbd0ab716f2d7f29d8dee50505b3fdb40c6b985c7c4f5a3591f"
], ],
"markers": "python_version >= '3.5'", "markers": "python_version >= '3.5'",
"version": "==2.0.4" "version": "==2.0.6"
}, },
"click": { "click": {
"hashes": [ "hashes": [
@@ -115,10 +115,10 @@
}, },
"embit": { "embit": {
"hashes": [ "hashes": [
"sha256:19f69929caf0d2fcfd4b708dd873384dfc36267944d02d5e6dfebc835f294e1b" "sha256:992332bd89af6e2d027e26fe437eb14aa33997db08c882c49064d49c3e6f4ab9"
], ],
"index": "pypi", "index": "pypi",
"version": "==0.4.6" "version": "==0.4.9"
}, },
"environs": { "environs": {
"hashes": [ "hashes": [
@@ -146,11 +146,11 @@
}, },
"httpcore": { "httpcore": {
"hashes": [ "hashes": [
"sha256:b0d16f0012ec88d8cc848f5a55f8a03158405f4bca02ee49bc4ca2c1fda49f3e", "sha256:036f960468759e633574d7c121afba48af6419615d36ab8ede979f1ad6276fa3",
"sha256:db4c0dcb8323494d01b8c6d812d80091a31e520033e7b0120883d6f52da649ff" "sha256:369aa481b014cf046f7067fddd67d00560f2f00426e79569d99cb11245134af0"
], ],
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.6'",
"version": "==0.13.6" "version": "==0.13.7"
}, },
"httptools": { "httptools": {
"hashes": [ "hashes": [
@@ -195,6 +195,14 @@
"markers": "python_version < '3.8'", "markers": "python_version < '3.8'",
"version": "==4.8.1" "version": "==4.8.1"
}, },
"jinja2": {
"hashes": [
"sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4",
"sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"
],
"index": "pypi",
"version": "==3.0.1"
},
"lnurl": { "lnurl": {
"hashes": [ "hashes": [
"sha256:579982fd8c4d25bc84c61c74ec45cb7999fa1fa2426f5d5aeb0160ba333b9c92", "sha256:579982fd8c4d25bc84c61c74ec45cb7999fa1fa2426f5d5aeb0160ba333b9c92",
@@ -203,6 +211,66 @@
"index": "pypi", "index": "pypi",
"version": "==0.3.6" "version": "==0.3.6"
}, },
"markupsafe": {
"hashes": [
"sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298",
"sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64",
"sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b",
"sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567",
"sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff",
"sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724",
"sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74",
"sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646",
"sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35",
"sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6",
"sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6",
"sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad",
"sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26",
"sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38",
"sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac",
"sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7",
"sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6",
"sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75",
"sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f",
"sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135",
"sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8",
"sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a",
"sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a",
"sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9",
"sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864",
"sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914",
"sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18",
"sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8",
"sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2",
"sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d",
"sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b",
"sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b",
"sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f",
"sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb",
"sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833",
"sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28",
"sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415",
"sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902",
"sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d",
"sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9",
"sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d",
"sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145",
"sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066",
"sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c",
"sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1",
"sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f",
"sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53",
"sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134",
"sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85",
"sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5",
"sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94",
"sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509",
"sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51",
"sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"
],
"markers": "python_version >= '3.6'",
"version": "==2.0.1"
},
"marshmallow": { "marshmallow": {
"hashes": [ "hashes": [
"sha256:c67929438fd73a2be92128caa0325b1b5ed8b626d91a094d2f7f2771bf1f1c0e", "sha256:c67929438fd73a2be92128caa0325b1b5ed8b626d91a094d2f7f2771bf1f1c0e",
@@ -457,12 +525,12 @@
}, },
"typing-extensions": { "typing-extensions": {
"hashes": [ "hashes": [
"sha256:045dd532231acfa03628df5e0c66dba64e2cc8fc8b844538d4ad6d5dd6cb82dc", "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e",
"sha256:83af6730a045fda60f46510f7f1f094776d90321caa4d97d20ef38871bef4bd3", "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7",
"sha256:8bbffbd37fbeb9747a0241fdfde5ae99d4531ad1d1a41ccaea62100e15a5814c" "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"
], ],
"index": "pypi", "index": "pypi",
"version": "==3.10.0.1" "version": "==3.10.0.2"
}, },
"uvicorn": { "uvicorn": {
"extras": [ "extras": [
@@ -505,41 +573,33 @@
}, },
"websockets": { "websockets": {
"hashes": [ "hashes": [
"sha256:0dd4eb8e0bbf365d6f652711ce21b8fd2b596f873d32aabb0fbb53ec604418cc", "sha256:01db0ecd1a0ca6702d02a5ed40413e18b7d22f94afb3bbe0d323bac86c42c1c8",
"sha256:1d0971cc7251aeff955aa742ec541ee8aaea4bb2ebf0245748fbec62f744a37e", "sha256:085bb8a6e780d30eaa1ba48ac7f3a6707f925edea787cfb761ce5a39e77ac09b",
"sha256:1d6b4fddb12ab9adf87b843cd4316c4bd602db8d5efd2fb83147f0458fe85135", "sha256:1ac35426fe3e7d3d0fac3d63c8965c76ed67a8fd713937be072bf0ce22808539",
"sha256:230a3506df6b5f446fed2398e58dcaafdff12d67fe1397dff196411a9e820d02", "sha256:1f6b814cff6aadc4288297cb3a248614829c6e4ff5556593c44a115e9dd49939",
"sha256:276d2339ebf0df4f45df453923ebd2270b87900eda5dfd4a6b0cfa15f82111c3", "sha256:2a43072e434c041a99f2e1eb9b692df0232a38c37c61d00e9f24db79474329e4",
"sha256:2cf04601633a4ec176b9cc3d3e73789c037641001dbfaf7c411f89cd3e04fcaf", "sha256:5b2600e01c7ca6f840c42c747ffbe0254f319594ed108db847eb3d75f4aacb80",
"sha256:3ddff38894c7857c476feb3538dd847514379d6dc844961dc99f04b0384b1b1b", "sha256:62160772314920397f9d219147f958b33fa27a12c662d4455c9ccbba9a07e474",
"sha256:48c222feb3ced18f3dc61168ca18952a22fb88e5eb8902d2bf1b50faefdc34a2", "sha256:706e200fc7f03bed99ad0574cd1ea8b0951477dd18cc978ccb190683c69dba76",
"sha256:51d04df04ed9d08077d10ccbe21e6805791b78eac49d16d30a1f1fe2e44ba0af", "sha256:71358c7816e2762f3e4af3adf0040f268e219f5a38cb3487a9d0fc2e554fef6a",
"sha256:597c28f3aa7a09e8c070a86b03107094ee5cdafcc0d55f2f2eac92faac8dc67d", "sha256:7d2e12e4f901f1bc062dfdf91831712c4106ed18a9a4cdb65e2e5f502124ca37",
"sha256:5c8f0d82ea2468282e08b0cf5307f3ad022290ed50c45d5cb7767957ca782880", "sha256:7f79f02c7f9a8320aff7d3321cd1c7e3a7dbc15d922ac996cca827301ee75238",
"sha256:7189e51955f9268b2bdd6cc537e0faa06f8fffda7fb386e5922c6391de51b077", "sha256:82b17524b1ce6ae7f7dd93e4d18e9b9474071e28b65dbf1dfe9b5767778db379",
"sha256:7df3596838b2a0c07c6f6d67752c53859a54993d4f062689fdf547cb56d0f84f", "sha256:82bd921885231f4a30d9bc550552495b3fc36b1235add6d374e7c65c3babd805",
"sha256:826ccf85d4514609219725ba4a7abd569228c2c9f1968e8be05be366f68291ec", "sha256:8bbf8660c3f833ddc8b1afab90213f2e672a9ddac6eecb3cde968e6b2807c1c7",
"sha256:836d14eb53b500fd92bd5db2fc5894f7c72b634f9c2a28f546f75967503d8e25", "sha256:9a4d889162bd48588e80950e07fa5e039eee9deb76a58092e8c3ece96d7ef537",
"sha256:85db8090ba94e22d964498a47fdd933b8875a1add6ebc514c7ac8703eb97bbf0", "sha256:b4ade7569b6fd17912452f9c3757d96f8e4044016b6d22b3b8391e641ca50456",
"sha256:85e701a6c316b7067f1e8675c638036a796fe5116783a4c932e7eb8e305a3ffe", "sha256:b8176deb6be540a46695960a765a77c28ac8b2e3ef2ec95d50a4f5df901edb1c",
"sha256:900589e19200be76dd7cbaa95e9771605b5ce3f62512d039fb3bc5da9014912a", "sha256:c4fc9a1d242317892590abe5b61a9127f1a61740477bfb121743f290b8054002",
"sha256:9147868bb0cc01e6846606cd65cbf9c58598f187b96d14dd1ca17338b08793bb", "sha256:c5880442f5fc268f1ef6d37b2c152c114deccca73f48e3a8c48004d2f16f4567",
"sha256:9e7fdc775fe7403dbd8bc883ba59576a6232eac96dacb56512daacf7af5d618d", "sha256:cd8c6f2ec24aedace251017bc7a414525171d4e6578f914acab9349362def4da",
"sha256:ab5ee15d3462198c794c49ccd31773d8a2b8c17d622aa184f669d2b98c2f0857", "sha256:d67646ddd17a86117ae21c27005d83c1895c0cef5d7be548b7549646372f868a",
"sha256:ad893d889bc700a5835e0a95a3e4f2c39e91577ab232a3dc03c262a0f8fc4b5c", "sha256:e42a1f1e03437b017af341e9bbfdc09252cd48ef32a8c3c3ead769eab3b17368",
"sha256:b2e71c4670ebe1067fa8632f0d081e47254ee2d3d409de54168b43b0ba9147e0", "sha256:eb282127e9c136f860c6068a4fba5756eb25e755baffb5940b6f1eae071928b2",
"sha256:b43b13e5622c5a53ab12f3272e6f42f1ce37cd5b6684b2676cb365403295cd40", "sha256:fe83b3ec9ef34063d86dfe1029160a85f24a5a94271036e5714a57acfdd089a1",
"sha256:b4ad84b156cf50529b8ac5cc1638c2cf8680490e3fccb6121316c8c02620a2e4", "sha256:ff59c6bdb87b31f7e2d596f09353d5a38c8c8ff571b0e2238e8ee2d55ad68465"
"sha256:be5fd35e99970518547edc906efab29afd392319f020c3c58b0e1a158e16ed20",
"sha256:caa68c95bc1776d3521f81eeb4d5b9438be92514ec2a79fececda814099c8314",
"sha256:d144b350045c53c8ff09aa1cfa955012dd32f00c7e0862c199edcabb1a8b32da",
"sha256:d2c2d9b24d3c65b5a02cac12cbb4e4194e590314519ed49db2f67ef561c3cf58",
"sha256:e9e5fd6dbdf95d99bc03732ded1fc8ef22ebbc05999ac7e0c7bf57fe6e4e5ae2",
"sha256:ebf459a1c069f9866d8569439c06193c586e72c9330db1390af7c6a0a32c4afd",
"sha256:f31722f1c033c198aa4a39a01905951c00bd1c74f922e8afc1b1c62adbcdd56a",
"sha256:f68c352a68e5fdf1e97288d5cec9296664c590c25932a8476224124aaf90dbcd"
], ],
"version": "==9.1" "version": "==10.0"
}, },
"zipp": { "zipp": {
"hashes": [ "hashes": [
@@ -707,11 +767,11 @@
}, },
"pluggy": { "pluggy": {
"hashes": [ "hashes": [
"sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159",
"sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "markers": "python_version >= '3.6'",
"version": "==0.13.1" "version": "==1.0.0"
}, },
"py": { "py": {
"hashes": [ "hashes": [
@@ -731,11 +791,11 @@
}, },
"pytest": { "pytest": {
"hashes": [ "hashes": [
"sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b", "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89",
"sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890" "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"
], ],
"index": "pypi", "index": "pypi",
"version": "==6.2.4" "version": "==6.2.5"
}, },
"pytest-cov": { "pytest-cov": {
"hashes": [ "hashes": [
@@ -837,12 +897,12 @@
}, },
"typing-extensions": { "typing-extensions": {
"hashes": [ "hashes": [
"sha256:045dd532231acfa03628df5e0c66dba64e2cc8fc8b844538d4ad6d5dd6cb82dc", "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e",
"sha256:83af6730a045fda60f46510f7f1f094776d90321caa4d97d20ef38871bef4bd3", "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7",
"sha256:8bbffbd37fbeb9747a0241fdfde5ae99d4531ad1d1a41ccaea62100e15a5814c" "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"
], ],
"index": "pypi", "index": "pypi",
"version": "==3.10.0.1" "version": "==3.10.0.2"
}, },
"zipp": { "zipp": {
"hashes": [ "hashes": [

View File

@@ -65,7 +65,7 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
register_routes(app) register_routes(app)
# register_commands(app) # register_commands(app)
register_async_tasks(app) register_async_tasks(app)
# register_exception_handlers(app) register_exception_handlers(app)
return app return app
@@ -96,9 +96,14 @@ def register_routes(app: FastAPI) -> None:
ext_module = importlib.import_module(f"lnbits.extensions.{ext.code}") ext_module = importlib.import_module(f"lnbits.extensions.{ext.code}")
ext_route = getattr(ext_module, f"{ext.code}_ext") ext_route = getattr(ext_module, f"{ext.code}_ext")
ext_statics = getattr(ext_module, f"{ext.code}_static_files") if hasattr(ext_module, f"{ext.code}_start"):
for s in ext_statics: ext_start_func = getattr(ext_module, f"{ext.code}_start")
app.mount(s["path"], s["app"], s["name"]) ext_start_func()
if hasattr(ext_module, f"{ext.code}_static_files"):
ext_statics = getattr(ext_module, f"{ext.code}_static_files")
for s in ext_statics:
app.mount(s["path"], s["app"], s["name"])
app.include_router(ext_route) app.include_router(ext_route)
except Exception as e: except Exception as e:
@@ -145,11 +150,11 @@ def register_async_tasks(app):
async def stop_listeners(): async def stop_listeners():
pass pass
def register_exception_handlers(app): def register_exception_handlers(app: FastAPI):
@app.errorhandler(Exception) @app.exception_handler(Exception)
async def basic_error(request: Request, err): async def basic_error(request: Request, err):
print("handled error", traceback.format_exc()) print("handled error", traceback.format_exc())
etype, value, tb = sys.exc_info() etype, _, tb = sys.exc_info()
traceback.print_exception(etype, err, tb) traceback.print_exception(etype, err, tb)
exc = traceback.format_exc() exc = traceback.format_exc()
return template_renderer().TemplateResponse("error.html", {"request": request, "err": err}) return template_renderer().TemplateResponse("error.html", {"request": request, "err": err})

View File

@@ -52,6 +52,7 @@ async def api_payments(wallet: WalletTypeInfo = Depends(get_key_type)):
class CreateInvoiceData(BaseModel): class CreateInvoiceData(BaseModel):
out: Optional[bool] = True
amount: int = Query(None, ge=1) amount: int = Query(None, ge=1)
memo: str = None memo: str = None
unit: Optional[str] = None unit: Optional[str] = None
@@ -60,6 +61,7 @@ class CreateInvoiceData(BaseModel):
lnurl_balance_check: Optional[str] = None lnurl_balance_check: Optional[str] = None
extra: Optional[dict] = None extra: Optional[dict] = None
webhook: Optional[str] = None webhook: Optional[str] = None
bolt11: Optional[str] = None
async def api_payments_create_invoice(data: CreateInvoiceData, wallet: Wallet): async def api_payments_create_invoice(data: CreateInvoiceData, wallet: Wallet):
if "description_hash" in data: if "description_hash" in data:
@@ -169,17 +171,16 @@ async def api_payments_pay_invoice(bolt11: str, wallet: Wallet):
@core_app.post("/api/v1/payments", deprecated=True, @core_app.post("/api/v1/payments", deprecated=True,
description="DEPRECATED. Use /api/v2/TBD and /api/v2/TBD instead", description="DEPRECATED. Use /api/v2/TBD and /api/v2/TBD instead",
status_code=HTTPStatus.CREATED) status_code=HTTPStatus.CREATED)
async def api_payments_create(wallet: WalletTypeInfo = Depends(get_key_type), out: bool = True, async def api_payments_create(wallet: WalletTypeInfo = Depends(get_key_type),
invoiceData: Optional[CreateInvoiceData] = Body(None), invoiceData: CreateInvoiceData = Body(...)):
bolt11: Optional[str] = Body(None)):
if wallet.wallet_type < 0 or wallet.wallet_type > 2: if wallet.wallet_type < 0 or wallet.wallet_type > 2:
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail="Key is invalid") raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail="Key is invalid")
if out is True and wallet.wallet_type == 0: if invoiceData.out is True and wallet.wallet_type == 0:
if not bolt11: if not invoiceData.bolt11:
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail="BOLT11 string is invalid or not given") raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail="BOLT11 string is invalid or not given")
return await api_payments_pay_invoice(bolt11, wallet.wallet) # admin key return await api_payments_pay_invoice(invoiceData.bolt11, wallet.wallet) # admin key
return await api_payments_create_invoice(invoiceData, wallet.wallet) # invoice key return await api_payments_create_invoice(invoiceData, wallet.wallet) # invoice key
class CreateLNURLData(BaseModel): class CreateLNURLData(BaseModel):
@@ -189,8 +190,9 @@ class CreateLNURLData(BaseModel):
comment: Optional[str] = None comment: Optional[str] = None
description: Optional[str] = None description: Optional[str] = None
@core_app.post("/api/v1/payments/lnurl", dependencies=[Depends(WalletAdminKeyChecker())]) @core_app.post("/api/v1/payments/lnurl")
async def api_payments_pay_lnurl(data: CreateLNURLData): async def api_payments_pay_lnurl(data: CreateLNURLData,
wallet: WalletTypeInfo = Depends(get_key_type)):
domain = urlparse(data.callback).netloc domain = urlparse(data.callback).netloc
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
@@ -220,13 +222,13 @@ async def api_payments_pay_lnurl(data: CreateLNURLData):
if invoice.amount_msat != data.amount: if invoice.amount_msat != data.amount:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, status_code=HTTPStatus.BAD_REQUEST,
detail=f"{domain} returned an invalid invoice. Expected {g().data['amount']} msat, got {invoice.amount_msat}." detail=f"{domain} returned an invalid invoice. Expected {data['amount']} msat, got {invoice.amount_msat}."
) )
if invoice.description_hash != g().data["description_hash"]: if invoice.description_hash != data.description_hash:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, status_code=HTTPStatus.BAD_REQUEST,
detail=f"{domain} returned an invalid invoice. Expected description_hash == {g().data['description_hash']}, got {invoice.description_hash}." detail=f"{domain} returned an invalid invoice. Expected description_hash == {data['description_hash']}, got {invoice.description_hash}."
) )
@@ -238,7 +240,7 @@ async def api_payments_pay_lnurl(data: CreateLNURLData):
extra["comment"] = data.comment extra["comment"] = data.comment
payment_hash = await pay_invoice( payment_hash = await pay_invoice(
wallet_id=g().wallet.id, wallet_id=wallet.wallet.id,
payment_request=params["pr"], payment_request=params["pr"],
description=data.description, description=data.description,
extra=extra, extra=extra,

View File

@@ -96,6 +96,8 @@ async def get_key_type(r: Request,
await checker.__call__(r) await checker.__call__(r)
return WalletTypeInfo(0, checker.wallet) return WalletTypeInfo(0, checker.wallet)
except HTTPException as e: except HTTPException as e:
if e.status_code == HTTPStatus.BAD_REQUEST:
raise
if e.status_code == HTTPStatus.UNAUTHORIZED: if e.status_code == HTTPStatus.UNAUTHORIZED:
pass pass
except: except:
@@ -106,6 +108,8 @@ async def get_key_type(r: Request,
await checker.__call__(r) await checker.__call__(r)
return WalletTypeInfo(1, checker.wallet) return WalletTypeInfo(1, checker.wallet)
except HTTPException as e: except HTTPException as e:
if e.status_code == HTTPStatus.BAD_REQUEST:
raise
if e.status_code == HTTPStatus.UNAUTHORIZED: if e.status_code == HTTPStatus.UNAUTHORIZED:
return WalletTypeInfo(2, None) return WalletTypeInfo(2, None)
except: except:

View File

@@ -1,17 +1,33 @@
from quart import Blueprint import asyncio
from fastapi import APIRouter
from lnbits.db import Database from lnbits.db import Database
from lnbits.helpers import template_renderer
from lnbits.tasks import catch_everything_and_restart
db = Database("ext_lnticket") db = Database("ext_lnticket")
lnticket_ext: Blueprint = Blueprint( lnticket_ext: APIRouter = APIRouter(
"lnticket", __name__, static_folder="static", template_folder="templates" prefix="/lnticket",
tags=["LNTicket"]
# "lnticket", __name__, static_folder="static", template_folder="templates"
) )
def lnticket_renderer():
return template_renderer(
[
"lnbits/extensions/lnticket/templates",
]
)
from .views_api import * # noqa from .views_api import * # noqa
from .views import * # noqa from .views import * # noqa
from .tasks import register_listeners from .tasks import wait_for_paid_invoices
from lnbits.tasks import record_async
lnticket_ext.record(record_async(register_listeners)) def lnticket_start():
loop = asyncio.get_event_loop()
loop.create_task(catch_everything_and_restart(wait_for_paid_invoices))

View File

@@ -1,27 +1,24 @@
from lnbits.core.models import Wallet
from typing import List, Optional, Union from typing import List, Optional, Union
from lnbits.helpers import urlsafe_short_hash from lnbits.helpers import urlsafe_short_hash
from . import db from . import db
from .models import Tickets, Forms from .models import CreateFormData, CreateTicketData, Tickets, Forms
import httpx import httpx
async def create_ticket( async def create_ticket(
payment_hash: str, payment_hash: str,
wallet: str, wallet: str,
form: str, data: CreateTicketData
name: str,
email: str,
ltext: str,
sats: int,
) -> Tickets: ) -> Tickets:
await db.execute( await db.execute(
""" """
INSERT INTO lnticket.ticket (id, form, email, ltext, name, wallet, sats, paid) INSERT INTO lnticket.ticket (id, form, email, ltext, name, wallet, sats, paid)
VALUES (?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""", """,
(payment_hash, form, email, ltext, name, wallet, sats, False), (payment_hash, data.form, data.email, data.ltext, data.name, wallet, data.sats, False),
) )
ticket = await get_ticket(payment_hash) ticket = await get_ticket(payment_hash)
@@ -103,13 +100,8 @@ async def delete_ticket(ticket_id: str) -> None:
async def create_form( async def create_form(
*, data: CreateFormData,
wallet: str, wallet: Wallet,
name: str,
webhook: Optional[str] = None,
description: str,
amount: int,
flatrate: int,
) -> Forms: ) -> Forms:
form_id = urlsafe_short_hash() form_id = urlsafe_short_hash()
await db.execute( await db.execute(
@@ -117,7 +109,7 @@ async def create_form(
INSERT INTO lnticket.form2 (id, wallet, name, webhook, description, flatrate, amount, amountmade) INSERT INTO lnticket.form2 (id, wallet, name, webhook, description, flatrate, amount, amountmade)
VALUES (?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""", """,
(form_id, wallet, name, webhook, description, flatrate, amount, 0), (form_id, wallet.id, wallet.name, data.webhook, data.description, data.flatrate, data.amount, 0),
) )
form = await get_form(form_id) form = await get_form(form_id)

View File

@@ -1,10 +1,26 @@
from typing import Optional
from fastapi.param_functions import Query
from pydantic import BaseModel from pydantic import BaseModel
class CreateFormData(BaseModel):
name: str = Query(...)
webhook: str = Query(None)
description: str = Query(..., min_length=0)
amount: int = Query(..., ge=0)
flatrate: int = Query(...)
class CreateTicketData(BaseModel):
form: str = Query(...)
name: str = Query(...)
email: str = Query("")
ltext: str = Query(...)
sats: int = Query(..., ge=0)
class Forms(BaseModel): class Forms(BaseModel):
id: str id: str
wallet: str wallet: str
name: str name: str
webhook: str webhook: Optional[str]
description: str description: str
amount: int amount: int
flatrate: int flatrate: int

View File

@@ -1,23 +1,17 @@
import json import asyncio
import trio # type: ignore
from lnbits.core.models import Payment from lnbits.core.models import Payment
from lnbits.core.crud import create_payment from lnbits.tasks import register_invoice_listener
from lnbits.core import db as core_db
from lnbits.tasks import register_invoice_listener, internal_invoice_paid
from lnbits.helpers import urlsafe_short_hash
from .crud import get_ticket, set_ticket_paid from .crud import get_ticket, set_ticket_paid
async def register_listeners(): async def wait_for_paid_invoices():
invoice_paid_chan_send, invoice_paid_chan_recv = trio.open_memory_channel(2) invoice_queue = asyncio.Queue()
register_invoice_listener(invoice_paid_chan_send) register_invoice_listener(invoice_queue)
await wait_for_paid_invoices(invoice_paid_chan_recv)
while True:
async def wait_for_paid_invoices(invoice_paid_chan: trio.MemoryReceiveChannel): payment = await invoice_queue.get()
async for payment in invoice_paid_chan:
await on_invoice_paid(payment) await on_invoice_paid(payment)

View File

@@ -337,7 +337,7 @@
LNbits.api LNbits.api
.request( .request(
'GET', 'GET',
'/lnticket/api/v1/tickets?all_wallets', '/lnticket/api/v1/tickets?all_wallets=true',
this.g.user.wallets[0].inkey this.g.user.wallets[0].inkey
) )
.then(function (response) { .then(function (response) {
@@ -382,7 +382,7 @@
LNbits.api LNbits.api
.request( .request(
'GET', 'GET',
'/lnticket/api/v1/forms?all_wallets', '/lnticket/api/v1/forms?all_wallets=true',
this.g.user.wallets[0].inkey this.g.user.wallets[0].inkey
) )
.then(function (response) { .then(function (response) {

View File

@@ -1,32 +1,40 @@
from quart import g, abort, render_template from fastapi.param_functions import Depends
from starlette.exceptions import HTTPException
from starlette.responses import HTMLResponse
from lnbits.core.models import User
from lnbits.core.crud import get_wallet from lnbits.core.crud import get_wallet
from lnbits.decorators import check_user_exists, validate_uuids from lnbits.decorators import check_user_exists
from http import HTTPStatus from http import HTTPStatus
from . import lnticket_ext from . import lnticket_ext, lnticket_renderer
from .crud import get_form from .crud import get_form
from fastapi import FastAPI, Request from fastapi import FastAPI, Request
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
templates = Jinja2Templates(directory="templates") templates = Jinja2Templates(directory="templates")
@lnticket_ext.route("/") @lnticket_ext.get("/", response_class=HTMLResponse)
@validate_uuids(["usr"], required=True) # not needed as we automatically get the user with the given ID
@check_user_exists() # If no user with this ID is found, an error is raised
async def index(request: Request): # @validate_uuids(["usr"], required=True)
return await templates.TemplateResponse("lnticket/index.html", {"request": request,"user":g.user}) # @check_user_exists()
async def index(request: Request, user: User = Depends(check_user_exists)):
return lnticket_renderer().TemplateResponse("lnticket/index.html", {"request": request,"user": user.dict()})
@lnticket_ext.route("/<form_id>") @lnticket_ext.get("/{form_id}")
async def display(request: Request, form_id): async def display(request: Request, form_id):
form = await get_form(form_id) form = await get_form(form_id)
if not form: if not form:
abort(HTTPStatus.NOT_FOUND, "LNTicket does not exist.") raise HTTPException(
status_code=HTTPStatus.NOT_FOUND,
detail="LNTicket does not exist."
)
# abort(HTTPStatus.NOT_FOUND, "LNTicket does not exist.")
wallet = await get_wallet(form.wallet) wallet = await get_wallet(form.wallet)
return await templates.TemplateResponse( return lnticket_renderer().TemplateResponse(
"lnticket/display.html", "lnticket/display.html",
{"request": request, {"request": request,
"form_id":form.id, "form_id":form.id,

View File

@@ -1,15 +1,19 @@
from lnbits.extensions.lnticket.models import CreateFormData, CreateTicketData
import re import re
from quart import g, jsonify, request
from http import HTTPStatus from http import HTTPStatus
from typing import List
from fastapi import Query
from fastapi.params import Depends
from fastapi import FastAPI, Query
from fastapi.encoders import jsonable_encoder
from fastapi.responses import JSONResponse
from pydantic import BaseModel from pydantic import BaseModel
from starlette.exceptions import HTTPException
from starlette.requests import Request
from starlette.responses import HTMLResponse, JSONResponse # type: ignore
from lnbits.core.crud import get_user, get_wallet from lnbits.core.crud import get_user, get_wallet
from lnbits.core.services import create_invoice, check_invoice_status from lnbits.core.services import create_invoice, check_invoice_status
from lnbits.decorators import api_check_wallet_key, api_validate_post_request from lnbits.decorators import WalletTypeInfo, get_key_type
from . import lnticket_ext from . import lnticket_ext
from .crud import ( from .crud import (
@@ -30,29 +34,17 @@ from .crud import (
@lnticket_ext.get("/api/v1/forms") @lnticket_ext.get("/api/v1/forms")
@api_check_wallet_key("invoice") async def api_forms_get(r: Request, all_wallets: bool = Query(False), wallet: WalletTypeInfo = Depends(get_key_type)):
async def api_forms(): wallet_ids = [wallet.wallet.id]
wallet_ids = [g.wallet.id]
if "all_wallets" in request.args: if all_wallets:
wallet_ids = (await get_user(g.wallet.user)).wallet_ids wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids
return ( return [form.dict() for form in await get_forms(wallet_ids)]
[form._asdict() for form in await get_forms(wallet_ids)],
HTTPStatus.OK,
)
class CreateData(BaseModel): @lnticket_ext.post("/api/v1/forms", status_code=HTTPStatus.CREATED)
wallet: str = Query(...)
name: str = Query(...)
webhook: str = Query(None)
description: str = Query(..., min_length=0)
amount: int = Query(..., ge=0)
flatrate: int = Query(...)
@lnticket_ext.post("/api/v1/forms")
@lnticket_ext.put("/api/v1/forms/{form_id}") @lnticket_ext.put("/api/v1/forms/{form_id}")
@api_check_wallet_key("invoice") # @api_check_wallet_key("invoice")
# @api_validate_post_request( # @api_validate_post_request(
# schema={ # schema={
# "wallet": {"type": "string", "empty": False, "required": True}, # "wallet": {"type": "string", "empty": False, "required": True},
@@ -63,62 +55,69 @@ class CreateData(BaseModel):
# "flatrate": {"type": "integer", "required": True}, # "flatrate": {"type": "integer", "required": True},
# } # }
# ) # )
async def api_form_create(data: CreateData, form_id=None): async def api_form_create(data: CreateFormData, form_id=None, wallet: WalletTypeInfo = Depends(get_key_type)):
if form_id: if form_id:
form = await get_form(form_id) form = await get_form(form_id)
if not form: if not form:
return {"message": "Form does not exist."}, HTTPStatus.NOT_FOUND raise HTTPException(
status_code=HTTPStatus.NOT_FOUND,
detail=f"Form does not exist."
)
# return {"message": "Form does not exist."}, HTTPStatus.NOT_FOUND
if form.wallet != g.wallet.id: if form.wallet != wallet.wallet.id:
return jsonify{"message": "Not your form."}, HTTPStatus.FORBIDDEN raise HTTPException(
status_code=HTTPStatus.FORBIDDEN,
detail=f"Not your form."
)
# return {"message": "Not your form."}, HTTPStatus.FORBIDDEN
form = await update_form(form_id, **data) form = await update_form(form_id, **data)
else: else:
form = await create_form(**data) form = await create_form(data, wallet.wallet)
return form._asdict(), HTTPStatus.CREATED return form.dict()
@lnticket_ext.delete("/api/v1/forms/{form_id}") @lnticket_ext.delete("/api/v1/forms/{form_id}")
@api_check_wallet_key("invoice") # @api_check_wallet_key("invoice")
async def api_form_delete(form_id): async def api_form_delete(form_id, wallet: WalletTypeInfo = Depends(get_key_type)):
form = await get_form(form_id) form = await get_form(form_id)
if not form: if not form:
return {"message": "Form does not exist."}, HTTPStatus.NOT_FOUND raise HTTPException(
status_code=HTTPStatus.NOT_FOUND,
detail=f"Form does not exist."
)
# return {"message": "Form does not exist."}, HTTPStatus.NOT_FOUND
if form.wallet != g.wallet.id: if form.wallet != wallet.wallet.id:
return {"message": "Not your form."}, HTTPStatus.FORBIDDEN raise HTTPException(
status_code=HTTPStatus.FORBIDDEN,
detail=f"Not your form."
)
# return {"message": "Not your form."}, HTTPStatus.FORBIDDEN
await delete_form(form_id) await delete_form(form_id)
return "", HTTPStatus.NO_CONTENT # return "", HTTPStatus.NO_CONTENT
raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
#########tickets########## #########tickets##########
@lnticket_ext.get("/api/v1/tickets") @lnticket_ext.get("/api/v1/tickets")
@api_check_wallet_key("invoice") # @api_check_wallet_key("invoice")
async def api_tickets(all_wallets: bool = Query(None)): async def api_tickets(all_wallets: bool = Query(False), wallet: WalletTypeInfo = Depends(get_key_type)):
wallet_ids = [g.wallet.id] wallet_ids = [wallet.wallet.id]
if all_wallets: if all_wallets:
wallet_ids = (await get_user(g.wallet.user)).wallet_ids wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids
return ( return [form.dict() for form in await get_tickets(wallet_ids)]
[form._asdict() for form in await get_tickets(wallet_ids)],
HTTPStatus.OK,
)
class CreateTicketData(BaseModel): @lnticket_ext.post("/api/v1/tickets/{form_id}", status_code=HTTPStatus.CREATED)
form: str = Query(...)
name: str = Query(...)
email: str = Query("")
ltext: str = Query(...)
sats: int = Query(..., ge=0)
@lnticket_ext.post("/api/v1/tickets/{form_id}")
# @api_validate_post_request( # @api_validate_post_request(
# schema={ # schema={
# "form": {"type": "string", "empty": False, "required": True}, # "form": {"type": "string", "empty": False, "required": True},
@@ -131,66 +130,86 @@ class CreateTicketData(BaseModel):
async def api_ticket_make_ticket(data: CreateTicketData, form_id): async def api_ticket_make_ticket(data: CreateTicketData, form_id):
form = await get_form(form_id) form = await get_form(form_id)
if not form: if not form:
return {"message": "LNTicket does not exist."}, HTTPStatus.NOT_FOUND raise HTTPException(
status_code=HTTPStatus.NOT_FOUND,
detail=f"LNTicket does not exist."
)
# return {"message": "LNTicket does not exist."}, HTTPStatus.NOT_FOUND
nwords = len(re.split(r"\s+", data["ltext"])) nwords = len(re.split(r"\s+", data.ltext))
sats = data["sats"]
try: try:
payment_hash, payment_request = await create_invoice( payment_hash, payment_request = await create_invoice(
wallet_id=form.wallet, wallet_id=form.wallet,
amount=sats, amount=data.sats,
memo=f"ticket with {nwords} words on {form_id}", memo=f"ticket with {nwords} words on {form_id}",
extra={"tag": "lnticket"}, extra={"tag": "lnticket"},
) )
except Exception as e: except Exception as e:
return {"message": str(e)}, HTTPStatus.INTERNAL_SERVER_ERROR raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail=str(e)
)
# return {"message": str(e)}, HTTPStatus.INTERNAL_SERVER_ERROR
ticket = await create_ticket( ticket = await create_ticket(
payment_hash=payment_hash, wallet=form.wallet, **data payment_hash=payment_hash, wallet=form.wallet, data=data
) )
if not ticket: if not ticket:
return ( raise HTTPException(
{"message": "LNTicket could not be fetched."}, status_code=HTTPStatus.NOT_FOUND,
HTTPStatus.NOT_FOUND, detail="LNTicket could not be fetched."
) )
# return (
# {"message": "LNTicket could not be fetched."},
# HTTPStatus.NOT_FOUND,
# )
return return {
{"payment_hash": payment_hash, "payment_request": payment_request}, "payment_hash": payment_hash,
HTTPStatus.OK "payment_request": payment_request
}
@lnticket_ext.get("/api/v1/tickets/{payment_hash}") @lnticket_ext.get("/api/v1/tickets/{payment_hash}", status_code=HTTPStatus.OK)
async def api_ticket_send_ticket(payment_hash): async def api_ticket_send_ticket(payment_hash):
ticket = await get_ticket(payment_hash) ticket = await get_ticket(payment_hash)
try: try:
status = await check_invoice_status(ticket.wallet, payment_hash) status = await check_invoice_status(ticket.wallet, payment_hash)
is_paid = not status.pending is_paid = not status.pending
except Exception: except Exception:
return {"paid": False}, HTTPStatus.OK return {"paid": False}
if is_paid: if is_paid:
wallet = await get_wallet(ticket.wallet) wallet = await get_wallet(ticket.wallet)
payment = await wallet.get_payment(payment_hash) payment = await wallet.get_payment(payment_hash)
await payment.set_pending(False) await payment.set_pending(False)
ticket = await set_ticket_paid(payment_hash=payment_hash) ticket = await set_ticket_paid(payment_hash=payment_hash)
return {"paid": True}, HTTPStatus.OK return {"paid": True}
return {"paid": False}, HTTPStatus.OK return {"paid": False}
@lnticket_ext.delete("/api/v1/tickets/{ticket_id}") @lnticket_ext.delete("/api/v1/tickets/{ticket_id}")
@api_check_wallet_key("invoice") # @api_check_wallet_key("invoice")
async def api_ticket_delete(ticket_id): async def api_ticket_delete(ticket_id, wallet: WalletTypeInfo = Depends(get_key_type)):
ticket = await get_ticket(ticket_id) ticket = await get_ticket(ticket_id)
if not ticket: if not ticket:
return {"message": "Paywall does not exist."}, HTTPStatus.NOT_FOUND raise HTTPException(
status_code=HTTPStatus.NOT_FOUND,
detail=f"LNTicket does not exist."
)
# return {"message": "Paywall does not exist."}, HTTPStatus.NOT_FOUND
if ticket.wallet != g.wallet.id: if ticket.wallet != wallet.wallet.id:
return {"message": "Not your ticket."}, HTTPStatus.FORBIDDEN raise HTTPException(
status_code=HTTPStatus.FORBIDDEN,
detail="Not your ticket."
)
# return {"message": "Not your ticket."}, HTTPStatus.FORBIDDEN
await delete_ticket(ticket_id) await delete_ticket(ticket_id)
raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
return "", HTTPStatus.NO_CONTENT # return ""

View File

@@ -1,4 +1,5 @@
import hashlib import hashlib
from lnbits.extensions.offlineshop.models import Item
from fastapi.params import Query from fastapi.params import Query
from starlette.requests import Request from starlette.requests import Request
@@ -13,8 +14,8 @@ from .crud import get_shop, get_item
@offlineshop_ext.get("/lnurl/{item_id}", name="offlineshop.lnurl_response") @offlineshop_ext.get("/lnurl/{item_id}", name="offlineshop.lnurl_response")
async def lnurl_response(item_id: int = Query(...)): async def lnurl_response(req: Request, item_id: int = Query(...)):
item = await get_item(item_id) item = await get_item(item_id) # type: Item
if not item: if not item:
return {"status": "ERROR", "reason": "Item not found."} return {"status": "ERROR", "reason": "Item not found."}
@@ -28,7 +29,7 @@ async def lnurl_response(item_id: int = Query(...)):
) * 1000 ) * 1000
resp = LnurlPayResponse( resp = LnurlPayResponse(
callback=url_for("offlineshop.lnurl_callback", item_id=item.id, _external=True), callback=req.url_for("offlineshop.lnurl_callback", item_id=item.id),
min_sendable=price_msat, min_sendable=price_msat,
max_sendable=price_msat, max_sendable=price_msat,
metadata=await item.lnurlpay_metadata(), metadata=await item.lnurlpay_metadata(),
@@ -37,9 +38,9 @@ async def lnurl_response(item_id: int = Query(...)):
return resp.dict() return resp.dict()
@offlineshop_ext.get("/lnurl/cb/<item_id>") @offlineshop_ext.get("/lnurl/cb/{item_id}", name="offlineshop.lnurl_callback")
async def lnurl_callback(request: Request, item_id: int): async def lnurl_callback(request: Request, item_id: int):
item = await get_item(item_id) item = await get_item(item_id) # type: Item
if not item: if not item:
return {"status": "ERROR", "reason": "Couldn't find item."} return {"status": "ERROR", "reason": "Couldn't find item."}
@@ -52,7 +53,7 @@ async def lnurl_callback(request: Request, item_id: int):
min = price * 995 min = price * 995
max = price * 1010 max = price * 1010
amount_received = int(request.args.get("amount") or 0) amount_received = int(request.query_params.get("amount") or 0)
if amount_received < min: if amount_received < min:
return LnurlErrorResponse( return LnurlErrorResponse(
reason=f"Amount {amount_received} is smaller than minimum {min}." reason=f"Amount {amount_received} is smaller than minimum {min}."

View File

@@ -14,7 +14,8 @@ from .helpers import totp
shop_counters: Dict = {} shop_counters: Dict = {}
class ShopCounter(BaseModel): class ShopCounter():
wordlist: List[str]
fulfilled_payments: OrderedDict fulfilled_payments: OrderedDict
counter: int counter: int
@@ -88,6 +89,11 @@ class Item(BaseModel):
price: int price: int
unit: str unit: str
def lnurl(self, req: Request) -> str:
return lnurl_encode(
req.url_for("offlineshop.lnurl_response", item_id=self.id)
)
def values(self, req: Request): def values(self, req: Request):
values = self.dict() values = self.dict()
values["lnurl"] = lnurl_encode( values["lnurl"] = lnurl_encode(

View File

@@ -1,8 +1,9 @@
import time import time
from datetime import datetime from datetime import datetime
from http import HTTPStatus from http import HTTPStatus
from typing import List
from fastapi.params import Depends from fastapi.params import Depends, Query
from starlette.responses import HTMLResponse from starlette.responses import HTMLResponse
from lnbits.decorators import check_user_exists from lnbits.decorators import check_user_exists
@@ -10,6 +11,7 @@ from lnbits.core.models import Payment, User
from lnbits.core.crud import get_standalone_payment from lnbits.core.crud import get_standalone_payment
from . import offlineshop_ext, offlineshop_renderer from . import offlineshop_ext, offlineshop_renderer
from .models import Item
from .crud import get_item, get_shop from .crud import get_item, get_shop
from fastapi import Request, HTTPException from fastapi import Request, HTTPException
@@ -20,14 +22,14 @@ async def index(request: Request, user: User = Depends(check_user_exists)):
@offlineshop_ext.get("/print", response_class=HTMLResponse) @offlineshop_ext.get("/print", response_class=HTMLResponse)
async def print_qr_codes(request: Request): async def print_qr_codes(request: Request, items: List[int] = None):
items = [] items = []
for item_id in request.args.get("items").split(","): for item_id in request.query_params.get("items").split(","):
item = await get_item(item_id) item = await get_item(item_id) # type: Item
if item: if item:
items.append( items.append(
{ {
"lnurl": item.lnurl, "lnurl": item.lnurl(request),
"name": item.name, "name": item.name,
"price": f"{item.price} {item.unit}", "price": f"{item.price} {item.unit}",
} }
@@ -36,8 +38,9 @@ async def print_qr_codes(request: Request):
return offlineshop_renderer().TemplateResponse("offlineshop/print.html", {"request": request,"items":items}) return offlineshop_renderer().TemplateResponse("offlineshop/print.html", {"request": request,"items":items})
@offlineshop_ext.get("/confirmation") @offlineshop_ext.get("/confirmation/{p}", name="offlineshop.confirmation_code",
async def confirmation_code(p: str): response_class=HTMLResponse)
async def confirmation_code(p: str = Query(...)):
style = "<style>* { font-size: 100px}</style>" style = "<style>* { font-size: 100px}</style>"
payment_hash = p payment_hash = p

View File

@@ -1,51 +1,48 @@
aiofiles==0.6.0 aiofiles==0.7.0
async-generator==1.10 anyio==3.3.1
attrs==20.3.0 asgiref==3.4.1
asyncio==3.4.3
attrs==21.2.0
bech32==1.2.0 bech32==1.2.0
bitstring==3.1.7 bitstring==3.1.9
blinker==1.4 cerberus==1.3.4
brotli==1.0.9 certifi==2021.5.30
cerberus==1.3.3 charset-normalizer==2.0.6
certifi==2020.12.5 click==8.0.1
click==7.1.2 ecdsa==0.17.0
ecdsa==0.16.1 embit==0.4.9
embit==0.2.1 environs==9.3.3
environs==9.3.2 fastapi==0.68.1
h11==0.12.0 h11==0.12.0
h2==4.0.0 httpcore==0.13.7
hpack==4.0.0 httptools==0.2.0
httpcore==0.12.3 httpx==0.19.0
httpx==0.17.1 idna==3.2
hypercorn==0.11.2 importlib-metadata==4.8.1
hyperframe==6.0.0 jinja2==3.0.1
idna==3.1
itsdangerous==1.1.0
jinja2==2.11.3
lnurl==0.3.6 lnurl==0.3.6
markupsafe==1.1.1 markupsafe==2.0.1
marshmallow==3.11.1 marshmallow==3.13.0
outcome==1.1.0 outcome==1.1.0
priority==1.3.0 psycopg2-binary==2.9.1
pydantic==1.8.1 pydantic==1.8.2
pyngrok==5.0.5 pypng==0.0.21
pypng==0.0.20
pyqrcode==1.2.1 pyqrcode==1.2.1
pyscss==1.3.7 pyscss==1.3.7
python-dotenv==0.17.0 python-dotenv==0.19.0
quart==0.14.1 pyyaml==5.4.1
quart-compress==0.2.1
quart-cors==0.4.0
quart-trio==0.7.0
represent==1.6.0.post0 represent==1.6.0.post0
rfc3986==1.4.0 rfc3986==1.5.0
shortuuid==1.0.1 shortuuid==1.0.1
six==1.15.0 six==1.16.0
sniffio==1.2.0 sniffio==1.2.0
sortedcontainers==2.3.0
sqlalchemy==1.3.23 sqlalchemy==1.3.23
sqlalchemy-aio==0.16.0 sqlalchemy-aio==0.16.0
toml==0.10.2 sse-starlette==0.6.2
trio==0.16.0 starlette==0.14.2
typing-extensions==3.7.4.3 typing-extensions==3.10.0.2
werkzeug==1.0.1 uvicorn==0.15.0
wsproto==1.0.0 uvloop==0.16.0
watchgod==0.7
websockets==10.0
zipp==3.5.0