Merge branch 'dev' into backgroundimage

This commit is contained in:
Arc 2024-12-19 16:24:44 +00:00 committed by GitHub
commit f7ed194e02
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 203 additions and 42 deletions

View File

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

View File

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

View File

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

View File

@ -51,8 +51,8 @@ class AuditFilters(FilterModel):
class AuditCountStat(BaseModel):
field: str
total: float
field: str = ""
total: float = 0
class AuditStats(BaseModel):

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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"