mirror of
https://github.com/lnbits/lnbits.git
synced 2025-03-18 05:41:54 +01:00
Merge branch 'dev' into backgroundimage
This commit is contained in:
commit
f7ed194e02
1
.github/workflows/regtest.yml
vendored
1
.github/workflows/regtest.yml
vendored
@ -36,7 +36,6 @@ jobs:
|
||||
run: |
|
||||
git clone https://github.com/lnbits/legend-regtest-enviroment.git docker
|
||||
cd docker
|
||||
git checkout cln-24-11
|
||||
chmod +x ./tests
|
||||
./tests
|
||||
sudo chmod -R a+rwx .
|
||||
|
@ -12,11 +12,15 @@ Note that by default LNbits uses SQLite as its database, which is simple and eff
|
||||
|
||||
## Option 1 (recommended): Poetry
|
||||
|
||||
It is recommended to use the latest version of Poetry. Make sure you have Python version 3.9 or higher installed.
|
||||
It is recommended to use the latest version of Poetry. Make sure you have Python version `3.12` installed.
|
||||
|
||||
### Verify Python version
|
||||
### Install Python 3.12
|
||||
|
||||
```sh
|
||||
sudo add-apt-repository ppa:deadsnakes/ppa
|
||||
sudo apt update
|
||||
sudo apt install python3.12
|
||||
sudo apt-get install python3.12-dev # ensure correct headers needed for secp256k1
|
||||
python3 --version
|
||||
```
|
||||
|
||||
@ -28,16 +32,16 @@ curl -sSL https://install.python-poetry.org | python3 -
|
||||
export PATH="/home/user/.local/bin:$PATH"
|
||||
```
|
||||
|
||||
### install LNbits
|
||||
|
||||
```sh
|
||||
git clone https://github.com/lnbits/lnbits.git
|
||||
cd lnbits
|
||||
poetry env use 3.12
|
||||
git checkout main
|
||||
|
||||
poetry install --only main
|
||||
|
||||
cp .env.example .env
|
||||
# set funding source amongst other options
|
||||
nano .env
|
||||
# Optional: to set funding source amongst other options via the env `nano .env`
|
||||
```
|
||||
|
||||
#### Running the server
|
||||
@ -49,9 +53,16 @@ poetry run lnbits
|
||||
# Note that you have to add the line DEBUG=true in your .env file, too.
|
||||
```
|
||||
|
||||
#### LNbits-cli
|
||||
|
||||
```sh
|
||||
# A very useful terminal client for getting the supersuer ID, updating extensions, etc
|
||||
poetry run lnbits-cli --help
|
||||
```
|
||||
|
||||
#### Updating the server
|
||||
|
||||
```
|
||||
```sh
|
||||
cd lnbits
|
||||
# Stop LNbits with `ctrl + x`
|
||||
git pull
|
||||
|
@ -123,15 +123,20 @@ async def get_payments_paginated(
|
||||
clause.append("wallet_id = :wallet_id")
|
||||
|
||||
if complete and pending:
|
||||
pass
|
||||
clause.append(
|
||||
f"(status = '{PaymentState.SUCCESS}' OR status = '{PaymentState.PENDING}')"
|
||||
)
|
||||
elif complete:
|
||||
clause.append(
|
||||
f"((amount > 0 AND status = '{PaymentState.SUCCESS}') OR amount < 0)"
|
||||
f"""
|
||||
(
|
||||
status = '{PaymentState.SUCCESS}'
|
||||
OR (amount < 0 AND status = '{PaymentState.PENDING}')
|
||||
)
|
||||
"""
|
||||
)
|
||||
elif pending:
|
||||
clause.append(f"status = '{PaymentState.PENDING}'")
|
||||
else:
|
||||
pass
|
||||
|
||||
if outgoing and incoming:
|
||||
pass
|
||||
@ -289,8 +294,14 @@ async def get_payments_history(
|
||||
values = {
|
||||
"wallet_id": wallet_id,
|
||||
}
|
||||
# count outgoing payments if they are still pending
|
||||
where = [
|
||||
f"wallet_id = :wallet_id AND (status = '{PaymentState.SUCCESS}' OR amount < 0)"
|
||||
f"""
|
||||
wallet_id = :wallet_id AND (
|
||||
status = '{PaymentState.SUCCESS}'
|
||||
OR (amount < 0 AND status = '{PaymentState.PENDING}')
|
||||
)
|
||||
"""
|
||||
]
|
||||
transactions: list[dict] = await db.fetchall(
|
||||
f"""
|
||||
|
@ -51,8 +51,8 @@ class AuditFilters(FilterModel):
|
||||
|
||||
|
||||
class AuditCountStat(BaseModel):
|
||||
field: str
|
||||
total: float
|
||||
field: str = ""
|
||||
total: float = 0
|
||||
|
||||
|
||||
class AuditStats(BaseModel):
|
||||
|
@ -1,7 +1,7 @@
|
||||
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
|
||||
%} {% block page %}
|
||||
<div class="row q-col-gutter-md justify-center">
|
||||
<div class="col q-my-md">
|
||||
<div class="col q-mb-md">
|
||||
<q-btn
|
||||
:label="$t('save')"
|
||||
color="primary"
|
||||
|
@ -1,7 +1,7 @@
|
||||
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
|
||||
%} {% block page %}
|
||||
|
||||
<div class="row q-col-gutter-md justify-center q-mb-xl">
|
||||
<div class="row q-col-gutter-md justify-center q-mb-lg">
|
||||
<div class="col-lg-3 col-md-6 col-sm-12 text-center">
|
||||
<q-card class="q-pt-sm">
|
||||
<strong>Components</strong>
|
||||
|
@ -1,21 +1,5 @@
|
||||
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
|
||||
%} {{ window_vars(user, extensions) }}{% block page %}
|
||||
<div class="row q-col-gutter-md q-mb-md">
|
||||
<div class="col-sm-9 col-xs-12">
|
||||
<p class="text-h4 gt-sm" v-text="$t('extensions')"></p>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-3 col-xs-12 q-ml-auto">
|
||||
<q-input v-model="searchTerm" :label="$t('search_extensions')">
|
||||
<q-icon
|
||||
v-if="searchTerm !== ''"
|
||||
name="close"
|
||||
@click="searchTerm = ''"
|
||||
class="cursor-pointer"
|
||||
/>
|
||||
</q-input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row q-col-gutter-md q-mb-md">
|
||||
<div class="col-12">
|
||||
@ -36,6 +20,25 @@
|
||||
v-text="$t('only_admins_can_install')"
|
||||
></i>
|
||||
<q-space></q-space>
|
||||
<q-input
|
||||
:label="$t('search_extensions')"
|
||||
:dense="dense"
|
||||
class="float-right q-pr-xl"
|
||||
v-model="searchTerm"
|
||||
>
|
||||
<template v-slot:before>
|
||||
<q-icon name="search"> </q-icon>
|
||||
</template>
|
||||
<template v-slot:append>
|
||||
<q-icon
|
||||
v-if="searchTerm !== ''"
|
||||
name="close"
|
||||
@click="searchTerm = ''"
|
||||
class="cursor-pointer"
|
||||
>
|
||||
</q-icon>
|
||||
</template>
|
||||
</q-input>
|
||||
<q-badge
|
||||
v-if="g.user.admin && updatableExtensions?.length"
|
||||
@click="showUpdateAllDialog = true"
|
||||
|
@ -25,6 +25,68 @@
|
||||
} : ''"
|
||||
>
|
||||
{% endif %}
|
||||
<q-scroll-area
|
||||
v-if="!mobileSimple"
|
||||
style="
|
||||
height: 115px;
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
"
|
||||
>
|
||||
<div class="row no-wrap q-gutter-md q-pr-md">
|
||||
<q-card
|
||||
v-for="wallet in g.user.wallets"
|
||||
:key="wallet.id"
|
||||
class="wallet-list-card"
|
||||
bordered
|
||||
tag="a"
|
||||
:href="wallet.url"
|
||||
:style="
|
||||
g.wallet && g.wallet.id === wallet.id
|
||||
? `border: 1px solid ${primaryColor}; width: 250px; text-decoration: none;`
|
||||
: 'width: 250px; text-decoration: none;'
|
||||
"
|
||||
:class="{
|
||||
'active-wallet-card': g.wallet && g.wallet.id === wallet.id
|
||||
}"
|
||||
>
|
||||
<q-card-section>
|
||||
<div class="row items-center">
|
||||
<q-avatar
|
||||
size="lg"
|
||||
:color="
|
||||
g.wallet && g.wallet.id === wallet.id
|
||||
? $q.dark.isActive
|
||||
? 'primary'
|
||||
: 'primary'
|
||||
: 'grey-5'
|
||||
"
|
||||
>
|
||||
<q-icon
|
||||
name="flash_on"
|
||||
:size="$q.dark.isActive ? '21px' : '20px'"
|
||||
:color="$q.dark.isActive ? 'black' : 'grey-3'"
|
||||
></q-icon>
|
||||
</q-avatar>
|
||||
<div
|
||||
class="text-h6 q-pl-md"
|
||||
:class="{
|
||||
'text-bold': g.wallet && g.wallet.id === wallet.id
|
||||
}"
|
||||
v-text="wallet.name"
|
||||
></div>
|
||||
</div>
|
||||
<div class="row items-center q-pt-sm">
|
||||
<h6 class="q-my-none text-no-wrap">
|
||||
<strong v-text="wallet.fsat"></strong>
|
||||
<small> {{LNBITS_DENOMINATION}}</small>
|
||||
</h6>
|
||||
</div>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</div>
|
||||
</q-scroll-area>
|
||||
<q-card
|
||||
:style="$q.screen.lt.md ? {
|
||||
background: $q.screen.lt.md ? 'none !important': ''
|
||||
|
@ -35,7 +35,7 @@ from lnbits.settings import settings
|
||||
from lnbits.utils.exchange_rates import (
|
||||
allowed_currencies,
|
||||
fiat_amount_as_satoshis,
|
||||
get_fiat_rate_satoshis,
|
||||
get_fiat_rate_and_price_satoshis,
|
||||
satoshis_amount_as_fiat,
|
||||
)
|
||||
from lnbits.wallets import get_funding_source
|
||||
@ -235,7 +235,7 @@ async def api_exchange_rate_history() -> list[dict]:
|
||||
|
||||
@api_router.get("/api/v1/rate/{currency}")
|
||||
async def api_check_fiat_rate(currency: str) -> dict[str, float]:
|
||||
rate, price = await get_fiat_rate_satoshis(currency)
|
||||
rate, price = await get_fiat_rate_and_price_satoshis(currency)
|
||||
return {"rate": rate, "price": price}
|
||||
|
||||
|
||||
|
@ -108,8 +108,6 @@ async def api_payments_paginated(
|
||||
await update_pending_payments(key_info.wallet.id)
|
||||
page = await get_payments_paginated(
|
||||
wallet_id=key_info.wallet.id,
|
||||
pending=True,
|
||||
complete=True,
|
||||
filters=filters,
|
||||
)
|
||||
return page
|
||||
|
@ -58,7 +58,8 @@ window.app = Vue.createApp({
|
||||
inkeyHidden: true,
|
||||
adminkeyHidden: true,
|
||||
hasNfc: false,
|
||||
nfcReaderAbortController: null
|
||||
nfcReaderAbortController: null,
|
||||
primaryColor: this.$q.localStorage.getItem('lnbits.primaryColor')
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@ -672,6 +673,11 @@ window.app = Vue.createApp({
|
||||
watch: {
|
||||
updatePayments() {
|
||||
this.updateFiatBalance()
|
||||
},
|
||||
'$q.screen.gt.sm'(value) {
|
||||
if (value == true) {
|
||||
this.mobileSimple = false
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
@ -247,7 +247,7 @@ async def btc_price(currency: str) -> float:
|
||||
return sum(rates_values) / len(rates_values)
|
||||
|
||||
|
||||
async def get_fiat_rate_satoshis(currency: str) -> tuple[float, float]:
|
||||
async def get_fiat_rate_and_price_satoshis(currency: str) -> tuple[float, float]:
|
||||
price = await cache.save_result(
|
||||
lambda: btc_price(currency),
|
||||
f"btc-price-{currency}",
|
||||
@ -256,11 +256,16 @@ async def get_fiat_rate_satoshis(currency: str) -> tuple[float, float]:
|
||||
return float(100_000_000 / price), price
|
||||
|
||||
|
||||
async def get_fiat_rate_satoshis(currency: str) -> float:
|
||||
rate, _ = await get_fiat_rate_and_price_satoshis(currency)
|
||||
return rate
|
||||
|
||||
|
||||
async def fiat_amount_as_satoshis(amount: float, currency: str) -> int:
|
||||
rate, _ = await get_fiat_rate_satoshis(currency)
|
||||
rate = await get_fiat_rate_satoshis(currency)
|
||||
return int(amount * (rate))
|
||||
|
||||
|
||||
async def satoshis_amount_as_fiat(amount: float, currency: str) -> float:
|
||||
rate, _ = await get_fiat_rate_satoshis(currency)
|
||||
rate = await get_fiat_rate_satoshis(currency)
|
||||
return float(amount / rate)
|
||||
|
@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "lnbits"
|
||||
version = "1.0.0-rc6"
|
||||
version = "1.0.0-rc7"
|
||||
description = "LNbits, free and open-source Lightning wallet and accounts system."
|
||||
authors = ["Alan Bits <alan@lnbits.com>"]
|
||||
readme = "README.md"
|
||||
|
66
tests/unit/test_crud_payments.py
Normal file
66
tests/unit/test_crud_payments.py
Normal file
@ -0,0 +1,66 @@
|
||||
import pytest
|
||||
|
||||
from lnbits.core.crud import create_wallet, get_payments, update_payment
|
||||
from lnbits.core.models import PaymentState
|
||||
from lnbits.core.services import create_user_account, update_wallet_balance
|
||||
|
||||
|
||||
async def update_payments(payments):
|
||||
payments[0].status = PaymentState.FAILED
|
||||
await update_payment(payments[0])
|
||||
payments[1].status = PaymentState.PENDING
|
||||
await update_payment(payments[1])
|
||||
payments[2].status = PaymentState.PENDING
|
||||
await update_payment(payments[2])
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_crud_get_payments(app):
|
||||
|
||||
user = await create_user_account()
|
||||
wallet = await create_wallet(user_id=user.id)
|
||||
|
||||
for _ in range(11):
|
||||
await update_wallet_balance(wallet, 10)
|
||||
wallet.balance_msat += 10 * 1000
|
||||
await update_wallet_balance(wallet, -10)
|
||||
wallet.balance_msat += -10 * 1000
|
||||
|
||||
payments = await get_payments(wallet_id=wallet.id)
|
||||
assert len(payments) == 22, "should return 22 successful payments"
|
||||
|
||||
payments = await get_payments(wallet_id=wallet.id, incoming=True)
|
||||
assert len(payments) == 11, "should return 11 successful incoming payments"
|
||||
await update_payments(payments)
|
||||
|
||||
payments = await get_payments(wallet_id=wallet.id, outgoing=True)
|
||||
assert len(payments) == 11, "should return 11 successful outgoing payments"
|
||||
await update_payments(payments)
|
||||
|
||||
payments = await get_payments(wallet_id=wallet.id, pending=True)
|
||||
assert len(payments) == 4, "should return 4 pending payments"
|
||||
|
||||
# function signature should have Optional[bool] for complete and pending to make
|
||||
# this distinction possible
|
||||
payments = await get_payments(wallet_id=wallet.id, pending=False)
|
||||
assert len(payments) == 22, "should return all payments"
|
||||
|
||||
payments = await get_payments(wallet_id=wallet.id, complete=True, pending=True)
|
||||
assert len(payments) == 20, "should return 4 pending and 16 complete payments"
|
||||
|
||||
payments = await get_payments(wallet_id=wallet.id, complete=True, outgoing=True)
|
||||
assert (
|
||||
len(payments) == 10
|
||||
), "should return 8 complete outgoing payments and 2 pending outgoing payments"
|
||||
|
||||
payments = await get_payments(wallet_id=wallet.id)
|
||||
assert len(payments) == 22, "should return all payments"
|
||||
|
||||
payments = await get_payments(wallet_id=wallet.id, complete=True)
|
||||
assert (
|
||||
len(payments) == 18
|
||||
), "should return 14 successful payment and 4 pending payments"
|
||||
|
||||
# both false should return failed payments
|
||||
# payments = await get_payments(wallet_id=wallet.id, complete=False, pending=False)
|
||||
# assert len(payments) == 2, "should return 2 failed payment"
|
Loading…
x
Reference in New Issue
Block a user