Merge branch 'main' into FinalAdminUI
This commit is contained in:
commit
2eeef5c1ed
|
@ -7,7 +7,7 @@ import traceback
|
|||
from http import HTTPStatus
|
||||
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from fastapi.exceptions import HTTPException, RequestValidationError
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.middleware.gzip import GZipMiddleware
|
||||
from fastapi.responses import JSONResponse
|
||||
|
@ -214,12 +214,33 @@ def register_async_tasks(app):
|
|||
|
||||
def register_exception_handlers(app: FastAPI):
|
||||
@app.exception_handler(Exception)
|
||||
async def basic_error(request: Request, err):
|
||||
logger.error("handled error", traceback.format_exc())
|
||||
logger.error("ERROR:", err)
|
||||
async def exception_handler(request: Request, exc: Exception):
|
||||
etype, _, tb = sys.exc_info()
|
||||
traceback.print_exception(etype, err, tb)
|
||||
exc = traceback.format_exc()
|
||||
traceback.print_exception(etype, exc, tb)
|
||||
logger.error(f"Exception: {str(exc)}")
|
||||
# Only the browser sends "text/html" request
|
||||
# not fail proof, but everything else get's a JSON response
|
||||
if (
|
||||
request.headers
|
||||
and "accept" in request.headers
|
||||
and "text/html" in request.headers["accept"]
|
||||
):
|
||||
return template_renderer().TemplateResponse(
|
||||
"error.html", {"request": request, "err": f"Error: {str(exc)}"}
|
||||
)
|
||||
|
||||
return JSONResponse(
|
||||
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
||||
content={"detail": str(exc)},
|
||||
)
|
||||
|
||||
@app.exception_handler(RequestValidationError)
|
||||
async def validation_exception_handler(
|
||||
request: Request, exc: RequestValidationError
|
||||
):
|
||||
logger.error(f"RequestValidationError: {str(exc)}")
|
||||
# Only the browser sends "text/html" request
|
||||
# not fail proof, but everything else get's a JSON response
|
||||
|
||||
if (
|
||||
request.headers
|
||||
|
@ -227,12 +248,37 @@ def register_exception_handlers(app: FastAPI):
|
|||
and "text/html" in request.headers["accept"]
|
||||
):
|
||||
return template_renderer().TemplateResponse(
|
||||
"error.html", {"request": request, "err": err}
|
||||
"error.html",
|
||||
{"request": request, "err": f"Error: {str(exc)}"},
|
||||
)
|
||||
|
||||
return JSONResponse(
|
||||
status_code=HTTPStatus.NO_CONTENT,
|
||||
content={"detail": err},
|
||||
status_code=HTTPStatus.BAD_REQUEST,
|
||||
content={"detail": str(exc)},
|
||||
)
|
||||
|
||||
@app.exception_handler(HTTPException)
|
||||
async def http_exception_handler(request: Request, exc: HTTPException):
|
||||
logger.error(f"HTTPException {exc.status_code}: {exc.detail}")
|
||||
# Only the browser sends "text/html" request
|
||||
# not fail proof, but everything else get's a JSON response
|
||||
|
||||
if (
|
||||
request.headers
|
||||
and "accept" in request.headers
|
||||
and "text/html" in request.headers["accept"]
|
||||
):
|
||||
return template_renderer().TemplateResponse(
|
||||
"error.html",
|
||||
{
|
||||
"request": request,
|
||||
"err": f"HTTP Error {exc.status_code}: {exc.detail}",
|
||||
},
|
||||
)
|
||||
|
||||
return JSONResponse(
|
||||
status_code=exc.status_code,
|
||||
content={"detail": exc.detail},
|
||||
)
|
||||
|
||||
@app.exception_handler(RequestValidationError)
|
||||
|
|
|
@ -221,7 +221,7 @@ async def mint_coins(
|
|||
|
||||
status: PaymentStatus = await check_transaction_status(cashu.wallet, payment_hash)
|
||||
|
||||
if status.paid != True:
|
||||
if LIGHTNING and status.paid != True:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.PAYMENT_REQUIRED, detail="Invoice not paid."
|
||||
)
|
||||
|
@ -265,37 +265,51 @@ async def melt_coins(
|
|||
detail="Error: Tokens are from another mint.",
|
||||
)
|
||||
|
||||
assert all([ledger._verify_proof(p) for p in proofs]), HTTPException(
|
||||
status_code=HTTPStatus.BAD_REQUEST,
|
||||
detail="Could not verify proofs.",
|
||||
)
|
||||
# set proofs as pending
|
||||
await ledger._set_proofs_pending(proofs)
|
||||
|
||||
total_provided = sum([p["amount"] for p in proofs])
|
||||
invoice_obj = bolt11.decode(invoice)
|
||||
amount = math.ceil(invoice_obj.amount_msat / 1000)
|
||||
try:
|
||||
ledger._verify_proofs(proofs)
|
||||
|
||||
internal_checking_id = await check_internal(invoice_obj.payment_hash)
|
||||
total_provided = sum([p["amount"] for p in proofs])
|
||||
invoice_obj = bolt11.decode(invoice)
|
||||
amount = math.ceil(invoice_obj.amount_msat / 1000)
|
||||
|
||||
if not internal_checking_id:
|
||||
fees_msat = fee_reserve(invoice_obj.amount_msat)
|
||||
else:
|
||||
fees_msat = 0
|
||||
assert total_provided >= amount + fees_msat / 1000, Exception(
|
||||
f"Provided proofs ({total_provided} sats) not enough for Lightning payment ({amount + fees_msat} sats)."
|
||||
)
|
||||
internal_checking_id = await check_internal(invoice_obj.payment_hash)
|
||||
|
||||
await pay_invoice(
|
||||
wallet_id=cashu.wallet,
|
||||
payment_request=invoice,
|
||||
description=f"pay cashu invoice",
|
||||
extra={"tag": "cashu", "cahsu_name": cashu.name},
|
||||
)
|
||||
if not internal_checking_id:
|
||||
fees_msat = fee_reserve(invoice_obj.amount_msat)
|
||||
else:
|
||||
fees_msat = 0
|
||||
assert total_provided >= amount + math.ceil(fees_msat / 1000), Exception(
|
||||
f"Provided proofs ({total_provided} sats) not enough for Lightning payment ({amount + fees_msat} sats)."
|
||||
)
|
||||
logger.debug(f"Cashu: Initiating payment of {total_provided} sats")
|
||||
await pay_invoice(
|
||||
wallet_id=cashu.wallet,
|
||||
payment_request=invoice,
|
||||
description=f"Pay cashu invoice",
|
||||
extra={"tag": "cashu", "cashu_name": cashu.name},
|
||||
)
|
||||
|
||||
logger.debug(
|
||||
f"Cashu: Wallet {cashu.wallet} checking PaymentStatus of {invoice_obj.payment_hash}"
|
||||
)
|
||||
status: PaymentStatus = await check_transaction_status(
|
||||
cashu.wallet, invoice_obj.payment_hash
|
||||
)
|
||||
if status.paid == True:
|
||||
logger.debug("Cashu: Payment successful, invalidating proofs")
|
||||
await ledger._invalidate_proofs(proofs)
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.BAD_REQUEST,
|
||||
detail=f"Cashu: {str(e)}",
|
||||
)
|
||||
finally:
|
||||
# delete proofs from pending list
|
||||
await ledger._unset_proofs_pending(proofs)
|
||||
|
||||
status: PaymentStatus = await check_transaction_status(
|
||||
cashu.wallet, invoice_obj.payment_hash
|
||||
)
|
||||
if status.paid == True:
|
||||
await ledger._invalidate_proofs(proofs)
|
||||
return GetMeltResponse(paid=status.paid, preimage=status.preimage)
|
||||
|
||||
|
||||
|
@ -333,7 +347,7 @@ async def check_fees(
|
|||
fees_msat = fee_reserve(invoice_obj.amount_msat)
|
||||
else:
|
||||
fees_msat = 0
|
||||
return CheckFeesResponse(fee=fees_msat / 1000)
|
||||
return CheckFeesResponse(fee=math.ceil(fees_msat / 1000))
|
||||
|
||||
|
||||
@cashu_ext.post("/api/v1/{cashu_id}/split")
|
||||
|
|
|
@ -37,7 +37,11 @@ async def call_webhook(charge: Charges):
|
|||
json=public_charge(charge),
|
||||
timeout=40,
|
||||
)
|
||||
return {"webhook_success": r.is_success, "webhook_message": r.reason_phrase}
|
||||
return {
|
||||
"webhook_success": r.is_success,
|
||||
"webhook_message": r.reason_phrase,
|
||||
"webhook_response": r.text,
|
||||
}
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to call webhook for charge {charge.id}")
|
||||
logger.warning(e)
|
||||
|
|
|
@ -23,6 +23,7 @@ const mapCharge = (obj, oldObj = {}) => {
|
|||
charge.displayUrl = ['/satspay/', obj.id].join('')
|
||||
charge.expanded = oldObj.expanded || false
|
||||
charge.pendingBalance = oldObj.pendingBalance || 0
|
||||
charge.extra = charge.extra ? JSON.parse(charge.extra) : charge.extra
|
||||
return charge
|
||||
}
|
||||
|
||||
|
|
|
@ -227,7 +227,12 @@
|
|||
>
|
||||
</div>
|
||||
<div class="col-4 q-pr-lg">
|
||||
<q-badge v-if="props.row.webhook_message" color="blue">
|
||||
<q-badge
|
||||
v-if="props.row.webhook_message"
|
||||
@click="showWebhookResponseDialog(props.row.extra.webhook_response)"
|
||||
color="blue"
|
||||
class="cursor-pointer"
|
||||
>
|
||||
{{props.row.webhook_message }}
|
||||
</q-badge>
|
||||
</div>
|
||||
|
@ -528,6 +533,23 @@
|
|||
</q-form>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
|
||||
<q-dialog v-model="showWebhookResponse" position="top">
|
||||
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
|
||||
<q-input
|
||||
filled
|
||||
dense
|
||||
readonly
|
||||
v-model.trim="webhookResponse"
|
||||
type="textarea"
|
||||
label="Response"
|
||||
></q-input>
|
||||
|
||||
<div class="row q-mt-lg">
|
||||
<q-btn flat v-close-popup color="grey" class="q-ml-auto">Close</q-btn>
|
||||
</div>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
</div>
|
||||
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
||||
<!-- lnbits/static/vendor
|
||||
|
@ -669,7 +691,9 @@
|
|||
data: {
|
||||
custom_css: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
showWebhookResponse: false,
|
||||
webhookResponse: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -757,7 +781,6 @@
|
|||
'/satspay/api/v1/themes',
|
||||
this.g.user.wallets[0].inkey
|
||||
)
|
||||
console.log(data)
|
||||
this.themeLinks = data.map(c =>
|
||||
mapCSS(
|
||||
c,
|
||||
|
@ -852,14 +875,12 @@
|
|||
},
|
||||
updateformDialog: function (themeId) {
|
||||
const theme = _.findWhere(this.themeLinks, {css_id: themeId})
|
||||
console.log(theme.css_id)
|
||||
this.formDialogThemes.data.css_id = theme.css_id
|
||||
this.formDialogThemes.data.title = theme.title
|
||||
this.formDialogThemes.data.custom_css = theme.custom_css
|
||||
this.formDialogThemes.show = true
|
||||
},
|
||||
createTheme: async function (wallet, data) {
|
||||
console.log(data.css_id)
|
||||
try {
|
||||
if (data.css_id) {
|
||||
const resp = await LNbits.api.request(
|
||||
|
@ -887,7 +908,6 @@
|
|||
custom_css: ''
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('cun')
|
||||
LNbits.utils.notifyApiError(error)
|
||||
}
|
||||
},
|
||||
|
@ -955,6 +975,10 @@
|
|||
}
|
||||
})
|
||||
},
|
||||
showWebhookResponseDialog(webhookResponse) {
|
||||
this.webhookResponse = webhookResponse
|
||||
this.showWebhookResponse = true
|
||||
},
|
||||
exportchargeCSV: function () {
|
||||
LNbits.utils.exportCSV(
|
||||
this.chargesTable.columns,
|
||||
|
|
9
poetry.lock
generated
9
poetry.lock
generated
|
@ -123,7 +123,7 @@ uvloop = ["uvloop (>=0.15.2)"]
|
|||
|
||||
[[package]]
|
||||
name = "cashu"
|
||||
version = "0.5.5"
|
||||
version = "0.6.0"
|
||||
description = "Ecash wallet and mint with Bitcoin Lightning support"
|
||||
category = "main"
|
||||
optional = false
|
||||
|
@ -1141,7 +1141,8 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools"
|
|||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.10 | ^3.9 | ^3.8 | ^3.7"
|
||||
content-hash = "cf27d11ac86c3967a097d781356fa7624b3978c7623711ac8dfc13edb09d7968"
|
||||
content-hash = "7f75ca0b067a11f19520dc2121f0789e16738b573a8da84ba3838ed8a466a6e1"
|
||||
|
||||
|
||||
[metadata.files]
|
||||
aiofiles = [
|
||||
|
@ -1205,8 +1206,8 @@ black = [
|
|||
{file = "black-22.10.0.tar.gz", hash = "sha256:f513588da599943e0cde4e32cc9879e825d58720d6557062d1098c5ad80080e1"},
|
||||
]
|
||||
cashu = [
|
||||
{file = "cashu-0.5.5-py3-none-any.whl", hash = "sha256:c1d707479b852e503acca5ed53aa341b1880cd6bd70369488ec002d647970c9b"},
|
||||
{file = "cashu-0.5.5.tar.gz", hash = "sha256:cc0349d3b6d9a2428cb575fee6280b20074ca9c20a1e2e9c68729a73c01f5f9d"},
|
||||
{file = "cashu-0.6.0-py3-none-any.whl", hash = "sha256:54096af145643aab45943b235f95a3357b0ec697835c1411e66523049ffb81f6"},
|
||||
{file = "cashu-0.6.0.tar.gz", hash = "sha256:503a90c4ca8d25d0b2c3f78a11b163c32902a726ea5b58e5337dc00eca8e96ad"},
|
||||
]
|
||||
cerberus = [
|
||||
{file = "Cerberus-1.3.4.tar.gz", hash = "sha256:d1b21b3954b2498d9a79edf16b3170a3ac1021df88d197dc2ce5928ba519237c"},
|
||||
|
|
|
@ -62,7 +62,7 @@ protobuf = "^4.21.6"
|
|||
Cerberus = "^1.3.4"
|
||||
async-timeout = "^4.0.2"
|
||||
pyln-client = "0.11.1"
|
||||
cashu = "0.5.5"
|
||||
cashu = "^0.6.0"
|
||||
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
|
|
|
@ -7,7 +7,7 @@ attrs==22.1.0 ; python_version >= "3.7" and python_version < "4.0"
|
|||
base58==2.1.1 ; python_version >= "3.7" and python_version < "4.0"
|
||||
bech32==1.2.0 ; python_version >= "3.7" and python_version < "4.0"
|
||||
bitstring==3.1.9 ; python_version >= "3.7" and python_version < "4.0"
|
||||
cashu==0.5.5 ; python_version >= "3.7" and python_version < "4.0"
|
||||
cashu==0.6.0 ; python_version >= "3.7" and python_version < "4.0"
|
||||
cerberus==1.3.4 ; python_version >= "3.7" and python_version < "4.0"
|
||||
certifi==2022.9.24 ; python_version >= "3.7" and python_version < "4.0"
|
||||
cffi==1.15.1 ; python_version >= "3.7" and python_version < "4.0"
|
||||
|
|
|
@ -46,11 +46,11 @@ async def test_get_wallet_no_redirect(client):
|
|||
i += 1
|
||||
|
||||
|
||||
# check GET /wallet: wrong user, expect 204
|
||||
# check GET /wallet: wrong user, expect 400
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_wallet_with_nonexistent_user(client):
|
||||
response = await client.get("wallet", params={"usr": "1"})
|
||||
assert response.status_code == 204, (
|
||||
assert response.status_code == 400, (
|
||||
str(response.url) + " " + str(response.status_code)
|
||||
)
|
||||
|
||||
|
@ -91,11 +91,11 @@ async def test_get_wallet_with_user_and_wallet(client, to_user, to_wallet):
|
|||
)
|
||||
|
||||
|
||||
# check GET /wallet: wrong wallet and user, expect 204
|
||||
# check GET /wallet: wrong wallet and user, expect 400
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_wallet_with_user_and_wrong_wallet(client, to_user):
|
||||
response = await client.get("wallet", params={"usr": to_user.id, "wal": "1"})
|
||||
assert response.status_code == 204, (
|
||||
assert response.status_code == 400, (
|
||||
str(response.url) + " " + str(response.status_code)
|
||||
)
|
||||
|
||||
|
@ -109,20 +109,20 @@ async def test_get_extensions(client, to_user):
|
|||
)
|
||||
|
||||
|
||||
# check GET /extensions: extensions list wrong user, expect 204
|
||||
# check GET /extensions: extensions list wrong user, expect 400
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_extensions_wrong_user(client, to_user):
|
||||
response = await client.get("extensions", params={"usr": "1"})
|
||||
assert response.status_code == 204, (
|
||||
assert response.status_code == 400, (
|
||||
str(response.url) + " " + str(response.status_code)
|
||||
)
|
||||
|
||||
|
||||
# check GET /extensions: no user given, expect code 204 no content
|
||||
# check GET /extensions: no user given, expect code 400 bad request
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_extensions_no_user(client):
|
||||
response = await client.get("extensions")
|
||||
assert response.status_code == 204, ( # no content
|
||||
assert response.status_code == 400, ( # bad request
|
||||
str(response.url) + " " + str(response.status_code)
|
||||
)
|
||||
|
||||
|
|
|
@ -61,21 +61,21 @@ async def test_endpoints_inkey(client, inkey_headers_to):
|
|||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.skipif(is_fake, reason="this test is only passes with regtest")
|
||||
async def test_endpoints_adminkey_nocontent(client, adminkey_headers_to):
|
||||
async def test_endpoints_adminkey_badrequest(client, adminkey_headers_to):
|
||||
response = await client.post("/boltz/api/v1/swap", headers=adminkey_headers_to)
|
||||
assert response.status_code == 204
|
||||
assert response.status_code == 400
|
||||
response = await client.post(
|
||||
"/boltz/api/v1/swap/reverse", headers=adminkey_headers_to
|
||||
)
|
||||
assert response.status_code == 204
|
||||
assert response.status_code == 400
|
||||
response = await client.post(
|
||||
"/boltz/api/v1/swap/refund", headers=adminkey_headers_to
|
||||
)
|
||||
assert response.status_code == 204
|
||||
assert response.status_code == 400
|
||||
response = await client.post(
|
||||
"/boltz/api/v1/swap/status", headers=adminkey_headers_to
|
||||
)
|
||||
assert response.status_code == 204
|
||||
assert response.status_code == 400
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
Loading…
Reference in New Issue
Block a user