Compare commits
12
Commits
v1.0.0-rc3
...
v1.0.0-rc4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f091ca343a | ||
|
|
1874a10342 | ||
|
|
c77cb07915 | ||
|
|
62d9470010 | ||
|
|
1c451674cf | ||
|
|
137e716bb8 | ||
|
|
75e07b21c7 | ||
|
|
b72a81aa4c | ||
|
|
22a16d27f1 | ||
|
|
8843726ae4 | ||
|
|
fa57b0de3f | ||
|
|
6989d9ab34 |
+2
-2
@@ -17,7 +17,7 @@ from slowapi.util import get_remote_address
|
|||||||
from starlette.middleware.sessions import SessionMiddleware
|
from starlette.middleware.sessions import SessionMiddleware
|
||||||
|
|
||||||
from lnbits.core.crud import (
|
from lnbits.core.crud import (
|
||||||
get_dbversions,
|
get_db_version,
|
||||||
get_installed_extensions,
|
get_installed_extensions,
|
||||||
update_installed_extension_state,
|
update_installed_extension_state,
|
||||||
)
|
)
|
||||||
@@ -313,7 +313,7 @@ async def restore_installed_extension(app: FastAPI, ext: InstallableExtension):
|
|||||||
extension = Extension.from_installable_ext(ext)
|
extension = Extension.from_installable_ext(ext)
|
||||||
register_ext_routes(app, extension)
|
register_ext_routes(app, extension)
|
||||||
|
|
||||||
current_version = (await get_dbversions()).get(ext.id, 0)
|
current_version = await get_db_version(ext.id)
|
||||||
await migrate_extension_database(ext, current_version)
|
await migrate_extension_database(ext, current_version)
|
||||||
|
|
||||||
# mount routes for the new version
|
# mount routes for the new version
|
||||||
|
|||||||
+8
-6
@@ -17,12 +17,13 @@ from lnbits.core.crud import (
|
|||||||
delete_unused_wallets,
|
delete_unused_wallets,
|
||||||
delete_wallet_by_id,
|
delete_wallet_by_id,
|
||||||
delete_wallet_payment,
|
delete_wallet_payment,
|
||||||
get_dbversions,
|
get_db_versions,
|
||||||
get_installed_extension,
|
get_installed_extension,
|
||||||
get_installed_extensions,
|
get_installed_extensions,
|
||||||
|
get_payment,
|
||||||
get_payments,
|
get_payments,
|
||||||
remove_deleted_wallets,
|
remove_deleted_wallets,
|
||||||
update_payment_status,
|
update_payment,
|
||||||
)
|
)
|
||||||
from lnbits.core.extensions.models import (
|
from lnbits.core.extensions.models import (
|
||||||
CreateExtension,
|
CreateExtension,
|
||||||
@@ -122,7 +123,7 @@ def database_migrate():
|
|||||||
async def db_versions():
|
async def db_versions():
|
||||||
"""Show current database versions"""
|
"""Show current database versions"""
|
||||||
async with core_db.connect() as conn:
|
async with core_db.connect() as conn:
|
||||||
click.echo(await get_dbversions(conn))
|
click.echo(await get_db_versions(conn))
|
||||||
|
|
||||||
|
|
||||||
@db.command("cleanup-wallets")
|
@db.command("cleanup-wallets")
|
||||||
@@ -172,9 +173,10 @@ async def database_delete_wallet_payment(wallet: str, checking_id: str):
|
|||||||
async def database_revert_payment(checking_id: str):
|
async def database_revert_payment(checking_id: str):
|
||||||
"""Mark payment as pending"""
|
"""Mark payment as pending"""
|
||||||
async with core_db.connect() as conn:
|
async with core_db.connect() as conn:
|
||||||
await update_payment_status(
|
payment = await get_payment(checking_id=checking_id, conn=conn)
|
||||||
status=PaymentState.PENDING, checking_id=checking_id, conn=conn
|
payment.status = PaymentState.PENDING
|
||||||
)
|
await update_payment(payment, conn)
|
||||||
|
click.echo(f"Payment '{checking_id}' marked as pending.")
|
||||||
|
|
||||||
|
|
||||||
@db.command("cleanup-accounts")
|
@db.command("cleanup-accounts")
|
||||||
|
|||||||
+48
-102
@@ -1,7 +1,7 @@
|
|||||||
import json
|
import json
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from time import time
|
from time import time
|
||||||
from typing import Literal, Optional, Union
|
from typing import Literal, Optional
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
import shortuuid
|
import shortuuid
|
||||||
@@ -25,6 +25,7 @@ from .models import (
|
|||||||
AccountFilters,
|
AccountFilters,
|
||||||
AccountOverview,
|
AccountOverview,
|
||||||
CreatePayment,
|
CreatePayment,
|
||||||
|
DbVersion,
|
||||||
Payment,
|
Payment,
|
||||||
PaymentFilters,
|
PaymentFilters,
|
||||||
PaymentHistoryPoint,
|
PaymentHistoryPoint,
|
||||||
@@ -161,15 +162,18 @@ async def get_account_by_username_or_email(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_user_by_id(
|
||||||
|
user_id: str, conn: Optional[Connection] = None
|
||||||
|
) -> Optional[User]:
|
||||||
|
account = await get_account(user_id, conn)
|
||||||
|
if not account:
|
||||||
|
return None
|
||||||
|
return await get_user(account, conn)
|
||||||
|
|
||||||
|
|
||||||
async def get_user(
|
async def get_user(
|
||||||
account_or_id: Union[Account, str], conn: Optional[Connection] = None
|
account: Account, conn: Optional[Connection] = None
|
||||||
) -> Optional[User]:
|
) -> Optional[User]:
|
||||||
if isinstance(account_or_id, str):
|
|
||||||
account = await get_account(account_or_id, conn)
|
|
||||||
if not account:
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
account = account_or_id
|
|
||||||
extensions = await get_user_active_extensions_ids(account.id, conn)
|
extensions = await get_user_active_extensions_ids(account.id, conn)
|
||||||
wallets = await get_wallets(account.id, False, conn=conn)
|
wallets = await get_wallets(account.id, False, conn=conn)
|
||||||
return User(
|
return User(
|
||||||
@@ -304,7 +308,7 @@ async def create_user_extension(
|
|||||||
async def update_user_extension(
|
async def update_user_extension(
|
||||||
user_extension: UserExtension, conn: Optional[Connection] = None
|
user_extension: UserExtension, conn: Optional[Connection] = None
|
||||||
) -> None:
|
) -> None:
|
||||||
where = """extension = :extension AND "user" = :user"""
|
where = """WHERE extension = :extension AND "user" = :user"""
|
||||||
await (conn or db).update("extensions", user_extension, where)
|
await (conn or db).update("extensions", user_extension, where)
|
||||||
|
|
||||||
|
|
||||||
@@ -476,6 +480,14 @@ async def get_total_balance(conn: Optional[Connection] = None):
|
|||||||
# ---------------
|
# ---------------
|
||||||
|
|
||||||
|
|
||||||
|
async def get_payment(checking_id: str, conn: Optional[Connection] = None) -> Payment:
|
||||||
|
return await (conn or db).fetchone(
|
||||||
|
"SELECT * FROM apipayments WHERE checking_id = :checking_id",
|
||||||
|
{"checking_id": checking_id},
|
||||||
|
Payment,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def get_standalone_payment(
|
async def get_standalone_payment(
|
||||||
checking_id_or_hash: str,
|
checking_id_or_hash: str,
|
||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
@@ -712,82 +724,12 @@ async def create_payment(
|
|||||||
return new_payment
|
return new_payment
|
||||||
|
|
||||||
|
|
||||||
async def update_payment_status(
|
async def update_payment(
|
||||||
checking_id: str, status: PaymentState, conn: Optional[Connection] = None
|
payment: Payment,
|
||||||
) -> None:
|
|
||||||
await (conn or db).execute(
|
|
||||||
"UPDATE apipayments SET status = :status WHERE checking_id = :checking_id",
|
|
||||||
{"status": status.value, "checking_id": checking_id},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def update_payment_details(
|
|
||||||
checking_id: str,
|
|
||||||
status: Optional[PaymentState] = None,
|
|
||||||
fee: Optional[int] = None,
|
|
||||||
preimage: Optional[str] = None,
|
|
||||||
new_checking_id: Optional[str] = None,
|
|
||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
set_variables: dict = {
|
await (conn or db).update(
|
||||||
"checking_id": checking_id,
|
"apipayments", payment, "WHERE checking_id = :checking_id"
|
||||||
"new_checking_id": new_checking_id,
|
|
||||||
"status": status.value if status else None,
|
|
||||||
"fee": fee,
|
|
||||||
"preimage": preimage,
|
|
||||||
}
|
|
||||||
|
|
||||||
set_clause: list[str] = []
|
|
||||||
if new_checking_id is not None:
|
|
||||||
set_clause.append("checking_id = :checking_id")
|
|
||||||
if status is not None:
|
|
||||||
set_clause.append("status = :status")
|
|
||||||
if fee is not None:
|
|
||||||
set_clause.append("fee = :fee")
|
|
||||||
if preimage is not None:
|
|
||||||
set_clause.append("preimage = :preimage")
|
|
||||||
|
|
||||||
await (conn or db).execute(
|
|
||||||
f"""
|
|
||||||
UPDATE apipayments SET {', '.join(set_clause)}
|
|
||||||
WHERE checking_id = :checking_id
|
|
||||||
""",
|
|
||||||
set_variables,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# TODO: should not be needed use update_payment instead
|
|
||||||
async def update_payment_extra(
|
|
||||||
payment_hash: str,
|
|
||||||
extra: dict,
|
|
||||||
outgoing: bool = False,
|
|
||||||
conn: Optional[Connection] = None,
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
Only update the `extra` field for the payment.
|
|
||||||
Old values in the `extra` JSON object will be kept
|
|
||||||
unless the new `extra` overwrites them.
|
|
||||||
"""
|
|
||||||
|
|
||||||
amount_clause = "AND amount < 0" if outgoing else "AND amount > 0"
|
|
||||||
|
|
||||||
row: dict = await (conn or db).fetchone(
|
|
||||||
f"""
|
|
||||||
SELECT payment_hash, extra from apipayments
|
|
||||||
WHERE payment_hash = :hash {amount_clause}
|
|
||||||
""",
|
|
||||||
{"hash": payment_hash},
|
|
||||||
)
|
|
||||||
if not row:
|
|
||||||
return
|
|
||||||
db_extra = json.loads(row["extra"] if row["extra"] else "{}")
|
|
||||||
db_extra.update(extra)
|
|
||||||
|
|
||||||
await (conn or db).execute(
|
|
||||||
f"""
|
|
||||||
UPDATE apipayments SET extra = :extra WHERE payment_hash = :hash {amount_clause}
|
|
||||||
""",
|
|
||||||
{"extra": json.dumps(db_extra), "hash": payment_hash},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -869,40 +811,38 @@ async def delete_wallet_payment(
|
|||||||
|
|
||||||
async def check_internal(
|
async def check_internal(
|
||||||
payment_hash: str, conn: Optional[Connection] = None
|
payment_hash: str, conn: Optional[Connection] = None
|
||||||
) -> Optional[str]:
|
) -> Optional[Payment]:
|
||||||
"""
|
"""
|
||||||
Returns the checking_id of the internal payment if it exists,
|
Returns the checking_id of the internal payment if it exists,
|
||||||
otherwise None
|
otherwise None
|
||||||
"""
|
"""
|
||||||
row: dict = await (conn or db).fetchone(
|
return await (conn or db).fetchone(
|
||||||
f"""
|
f"""
|
||||||
SELECT checking_id FROM apipayments
|
SELECT * FROM apipayments
|
||||||
WHERE payment_hash = :hash AND status = '{PaymentState.PENDING}' AND amount > 0
|
WHERE payment_hash = :hash AND status = '{PaymentState.PENDING}' AND amount > 0
|
||||||
""",
|
""",
|
||||||
{"hash": payment_hash},
|
{"hash": payment_hash},
|
||||||
|
Payment,
|
||||||
)
|
)
|
||||||
if not row:
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
return row["checking_id"]
|
|
||||||
|
|
||||||
|
|
||||||
async def check_internal_status(
|
async def is_internal_status_success(
|
||||||
payment_hash: str, conn: Optional[Connection] = None
|
payment_hash: str, conn: Optional[Connection] = None
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
Returns True if the internal payment was successful
|
Returns True if the internal payment was found and has the given status,
|
||||||
"""
|
"""
|
||||||
row: dict = await (conn or db).fetchone(
|
payment = await (conn or db).fetchone(
|
||||||
"""
|
"""
|
||||||
SELECT status FROM apipayments
|
SELECT * FROM apipayments
|
||||||
WHERE payment_hash = :payment_hash AND amount > 0
|
WHERE payment_hash = :payment_hash AND amount > 0
|
||||||
""",
|
""",
|
||||||
{"payment_hash": payment_hash},
|
{"payment_hash": payment_hash},
|
||||||
|
Payment,
|
||||||
)
|
)
|
||||||
if not row:
|
if not payment:
|
||||||
return True
|
return True
|
||||||
return row["status"] == PaymentState.SUCCESS.value
|
return payment.status == PaymentState.SUCCESS.value
|
||||||
|
|
||||||
|
|
||||||
async def mark_webhook_sent(payment_hash: str, status: int) -> None:
|
async def mark_webhook_sent(payment_hash: str, status: int) -> None:
|
||||||
@@ -982,12 +922,18 @@ async def create_admin_settings(super_user: str, new_settings: dict):
|
|||||||
|
|
||||||
# db versions
|
# db versions
|
||||||
# --------------
|
# --------------
|
||||||
async def get_dbversions(conn: Optional[Connection] = None) -> dict:
|
async def get_db_version(
|
||||||
result = await (conn or db).execute("SELECT db, version FROM dbversions")
|
ext_id: str, conn: Optional[Connection] = None
|
||||||
_dict = {}
|
) -> Optional[DbVersion]:
|
||||||
for row in result.mappings().all():
|
return await (conn or db).fetchone(
|
||||||
_dict[row["db"]] = row["version"]
|
"SELECT * FROM dbversions WHERE db = :ext_id",
|
||||||
return _dict
|
{"ext_id": ext_id},
|
||||||
|
model=DbVersion,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_db_versions(conn: Optional[Connection] = None) -> list[DbVersion]:
|
||||||
|
return await (conn or db).fetchall("SELECT * FROM dbversions", model=DbVersion)
|
||||||
|
|
||||||
|
|
||||||
async def update_migration_version(conn, db_name, version):
|
async def update_migration_version(conn, db_name, version):
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from lnbits.core import core_app_extra
|
|||||||
from lnbits.core.crud import (
|
from lnbits.core.crud import (
|
||||||
create_installed_extension,
|
create_installed_extension,
|
||||||
delete_installed_extension,
|
delete_installed_extension,
|
||||||
get_dbversions,
|
get_db_version,
|
||||||
get_installed_extension,
|
get_installed_extension,
|
||||||
update_installed_extension_state,
|
update_installed_extension_state,
|
||||||
)
|
)
|
||||||
@@ -28,7 +28,7 @@ async def install_extension(ext_info: InstallableExtension) -> Extension:
|
|||||||
|
|
||||||
ext_info.extract_archive()
|
ext_info.extract_archive()
|
||||||
|
|
||||||
db_version = (await get_dbversions()).get(ext_id, 0)
|
db_version = await get_db_version(ext_id)
|
||||||
await migrate_extension_database(ext_info, db_version)
|
await migrate_extension_database(ext_info, db_version)
|
||||||
|
|
||||||
await create_installed_extension(ext_info)
|
await create_installed_extension(ext_info)
|
||||||
|
|||||||
+20
-8
@@ -1,6 +1,6 @@
|
|||||||
import importlib
|
import importlib
|
||||||
import re
|
import re
|
||||||
from typing import Any
|
from typing import Any, Optional
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
@@ -8,17 +8,20 @@ from loguru import logger
|
|||||||
|
|
||||||
from lnbits.core import migrations as core_migrations
|
from lnbits.core import migrations as core_migrations
|
||||||
from lnbits.core.crud import (
|
from lnbits.core.crud import (
|
||||||
get_dbversions,
|
get_db_versions,
|
||||||
get_installed_extensions,
|
get_installed_extensions,
|
||||||
update_migration_version,
|
update_migration_version,
|
||||||
)
|
)
|
||||||
from lnbits.core.db import db as core_db
|
from lnbits.core.db import db as core_db
|
||||||
from lnbits.core.extensions.models import InstallableExtension
|
from lnbits.core.extensions.models import InstallableExtension
|
||||||
|
from lnbits.core.models import DbVersion
|
||||||
from lnbits.db import COCKROACH, POSTGRES, SQLITE, Connection
|
from lnbits.db import COCKROACH, POSTGRES, SQLITE, Connection
|
||||||
from lnbits.settings import settings
|
from lnbits.settings import settings
|
||||||
|
|
||||||
|
|
||||||
async def migrate_extension_database(ext: InstallableExtension, current_version: int):
|
async def migrate_extension_database(
|
||||||
|
ext: InstallableExtension, current_version: Optional[DbVersion] = None
|
||||||
|
):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ext_migrations = importlib.import_module(f"{ext.module_name}.migrations")
|
ext_migrations = importlib.import_module(f"{ext.module_name}.migrations")
|
||||||
@@ -32,14 +35,17 @@ async def migrate_extension_database(ext: InstallableExtension, current_version:
|
|||||||
|
|
||||||
|
|
||||||
async def run_migration(
|
async def run_migration(
|
||||||
db: Connection, migrations_module: Any, db_name: str, current_version: int
|
db: Connection,
|
||||||
|
migrations_module: Any,
|
||||||
|
db_name: str,
|
||||||
|
current_version: Optional[DbVersion] = None,
|
||||||
):
|
):
|
||||||
matcher = re.compile(r"^m(\d\d\d)_")
|
matcher = re.compile(r"^m(\d\d\d)_")
|
||||||
for key, migrate in migrations_module.__dict__.items():
|
for key, migrate in migrations_module.__dict__.items():
|
||||||
match = matcher.match(key)
|
match = matcher.match(key)
|
||||||
if match:
|
if match:
|
||||||
version = int(match.group(1))
|
version = int(match.group(1))
|
||||||
if version > current_version:
|
if not current_version or version > current_version.version:
|
||||||
logger.debug(f"running migration {db_name}.{version}")
|
logger.debug(f"running migration {db_name}.{version}")
|
||||||
print(f"running migration {db_name}.{version}")
|
print(f"running migration {db_name}.{version}")
|
||||||
await migrate(db)
|
await migrate(db)
|
||||||
@@ -71,7 +77,6 @@ async def load_disabled_extension_list() -> None:
|
|||||||
async def migrate_databases():
|
async def migrate_databases():
|
||||||
"""Creates the necessary databases if they don't exist already; or migrates them."""
|
"""Creates the necessary databases if they don't exist already; or migrates them."""
|
||||||
|
|
||||||
current_versions = await get_dbversions()
|
|
||||||
async with core_db.connect() as conn:
|
async with core_db.connect() as conn:
|
||||||
exists = False
|
exists = False
|
||||||
if conn.type == SQLITE:
|
if conn.type == SQLITE:
|
||||||
@@ -87,7 +92,11 @@ async def migrate_databases():
|
|||||||
if not exists:
|
if not exists:
|
||||||
await core_migrations.m000_create_migrations_table(conn)
|
await core_migrations.m000_create_migrations_table(conn)
|
||||||
|
|
||||||
core_version = current_versions.get("core", 0)
|
current_versions = await get_db_versions(conn)
|
||||||
|
core_version = next(
|
||||||
|
(v for v in current_versions if v.db == "core"),
|
||||||
|
DbVersion(db="core", version=0),
|
||||||
|
)
|
||||||
await run_migration(conn, core_migrations, "core", core_version)
|
await run_migration(conn, core_migrations, "core", core_version)
|
||||||
|
|
||||||
# here is the first place we can be sure that the
|
# here is the first place we can be sure that the
|
||||||
@@ -95,7 +104,10 @@ async def migrate_databases():
|
|||||||
await load_disabled_extension_list()
|
await load_disabled_extension_list()
|
||||||
|
|
||||||
for ext in await get_installed_extensions():
|
for ext in await get_installed_extensions():
|
||||||
current_version = current_versions.get(ext.id)
|
current_version = next(
|
||||||
|
(v for v in current_versions if v.db == ext.id),
|
||||||
|
DbVersion(db=ext.id, version=0),
|
||||||
|
)
|
||||||
if current_version is None:
|
if current_version is None:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"Extension {ext.id} has no migration version. This should not happen."
|
f"Extension {ext.id} has no migration version. This should not happen."
|
||||||
|
|||||||
@@ -562,6 +562,8 @@ async def m023_add_column_column_to_apipayments(db):
|
|||||||
await db.execute("ALTER TABLE apipayments RENAME COLUMN wallet TO wallet_id")
|
await db.execute("ALTER TABLE apipayments RENAME COLUMN wallet TO wallet_id")
|
||||||
await db.execute("ALTER TABLE accounts RENAME COLUMN pass TO password_hash")
|
await db.execute("ALTER TABLE accounts RENAME COLUMN pass TO password_hash")
|
||||||
|
|
||||||
|
await db.execute("CREATE INDEX by_hash ON apipayments (payment_hash)")
|
||||||
|
|
||||||
|
|
||||||
async def m024_drop_pending(db):
|
async def m024_drop_pending(db):
|
||||||
await db.execute("ALTER TABLE apipayments DROP COLUMN pending")
|
await db.execute("ALTER TABLE apipayments DROP COLUMN pending")
|
||||||
|
|||||||
+5
-10
@@ -2,7 +2,6 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import hmac
|
import hmac
|
||||||
import json
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
@@ -27,15 +26,6 @@ from lnbits.wallets.base import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def json_custom_serialization(_, o):
|
|
||||||
if isinstance(o, datetime):
|
|
||||||
return o.isoformat()
|
|
||||||
raise TypeError(f"Object is not JSON serializable: {o}")
|
|
||||||
|
|
||||||
|
|
||||||
json.JSONEncoder.default = json_custom_serialization # type: ignore[method-assign]
|
|
||||||
|
|
||||||
|
|
||||||
class BaseWallet(BaseModel):
|
class BaseWallet(BaseModel):
|
||||||
id: str
|
id: str
|
||||||
name: str
|
name: str
|
||||||
@@ -479,3 +469,8 @@ class BalanceDelta(BaseModel):
|
|||||||
class SimpleStatus(BaseModel):
|
class SimpleStatus(BaseModel):
|
||||||
success: bool
|
success: bool
|
||||||
message: str
|
message: str
|
||||||
|
|
||||||
|
|
||||||
|
class DbVersion(BaseModel):
|
||||||
|
db: str
|
||||||
|
version: int
|
||||||
|
|||||||
+68
-89
@@ -1,7 +1,6 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
from datetime import datetime
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
@@ -47,7 +46,6 @@ from lnbits.wallets.base import (
|
|||||||
|
|
||||||
from .crud import (
|
from .crud import (
|
||||||
check_internal,
|
check_internal,
|
||||||
check_internal_status,
|
|
||||||
create_account,
|
create_account,
|
||||||
create_admin_settings,
|
create_admin_settings,
|
||||||
create_payment,
|
create_payment,
|
||||||
@@ -56,6 +54,7 @@ from .crud import (
|
|||||||
get_account_by_email,
|
get_account_by_email,
|
||||||
get_account_by_pubkey,
|
get_account_by_pubkey,
|
||||||
get_account_by_username,
|
get_account_by_username,
|
||||||
|
get_payment,
|
||||||
get_payments,
|
get_payments,
|
||||||
get_standalone_payment,
|
get_standalone_payment,
|
||||||
get_super_settings,
|
get_super_settings,
|
||||||
@@ -63,9 +62,9 @@ from .crud import (
|
|||||||
get_user,
|
get_user,
|
||||||
get_wallet,
|
get_wallet,
|
||||||
get_wallet_payment,
|
get_wallet_payment,
|
||||||
|
is_internal_status_success,
|
||||||
update_admin_settings,
|
update_admin_settings,
|
||||||
update_payment_details,
|
update_payment,
|
||||||
update_payment_status,
|
|
||||||
update_super_user,
|
update_super_user,
|
||||||
update_user_extension,
|
update_user_extension,
|
||||||
)
|
)
|
||||||
@@ -247,19 +246,17 @@ async def pay_invoice(
|
|||||||
extra=extra,
|
extra=extra,
|
||||||
)
|
)
|
||||||
|
|
||||||
# we check if an internal invoice exists that has already been paid
|
if await is_internal_status_success(invoice.payment_hash, conn=conn):
|
||||||
# (not pending anymore)
|
|
||||||
if await check_internal_status(invoice.payment_hash, conn=conn):
|
|
||||||
raise PaymentError("Internal invoice already paid.", status="failed")
|
raise PaymentError("Internal invoice already paid.", status="failed")
|
||||||
|
|
||||||
# check_internal() returns the checking_id of the invoice we're waiting for
|
# check_internal() returns the checking_id of the invoice we're waiting for
|
||||||
# (pending only)
|
# (pending only)
|
||||||
internal_checking_id = await check_internal(invoice.payment_hash, conn=conn)
|
internal_payment = await check_internal(invoice.payment_hash, conn=conn)
|
||||||
if internal_checking_id:
|
if internal_payment:
|
||||||
# perform additional checks on the internal payment
|
# perform additional checks on the internal payment
|
||||||
# the payment hash is not enough to make sure that this is the same invoice
|
# the payment hash is not enough to make sure that this is the same invoice
|
||||||
internal_invoice = await get_standalone_payment(
|
internal_invoice = await get_standalone_payment(
|
||||||
internal_checking_id, incoming=True, conn=conn
|
internal_payment.checking_id, incoming=True, conn=conn
|
||||||
)
|
)
|
||||||
assert internal_invoice is not None
|
assert internal_invoice is not None
|
||||||
if (
|
if (
|
||||||
@@ -293,7 +290,7 @@ async def pay_invoice(
|
|||||||
wallet = await get_wallet(wallet_id, conn=conn)
|
wallet = await get_wallet(wallet_id, conn=conn)
|
||||||
assert wallet, "Wallet for balancecheck could not be fetched"
|
assert wallet, "Wallet for balancecheck could not be fetched"
|
||||||
fee_reserve_total_msat = fee_reserve_total(invoice.amount_msat, internal=False)
|
fee_reserve_total_msat = fee_reserve_total(invoice.amount_msat, internal=False)
|
||||||
_check_wallet_balance(wallet, fee_reserve_total_msat, internal_checking_id)
|
_check_wallet_balance(wallet, fee_reserve_total_msat, internal_payment)
|
||||||
|
|
||||||
if extra and "tag" in extra:
|
if extra and "tag" in extra:
|
||||||
# check if the payment is made for an extension that the user disabled
|
# check if the payment is made for an extension that the user disabled
|
||||||
@@ -301,79 +298,71 @@ async def pay_invoice(
|
|||||||
if not status.success:
|
if not status.success:
|
||||||
raise PaymentError(status.message)
|
raise PaymentError(status.message)
|
||||||
|
|
||||||
if internal_checking_id:
|
if internal_payment:
|
||||||
service_fee_msat = service_fee(invoice.amount_msat, internal=True)
|
service_fee_msat = service_fee(invoice.amount_msat, internal=True)
|
||||||
logger.debug(f"marking temporary payment as not pending {internal_checking_id}")
|
logger.debug(
|
||||||
|
f"marking temporary payment as not pending {internal_payment.checking_id}"
|
||||||
|
)
|
||||||
# mark the invoice from the other side as not pending anymore
|
# mark the invoice from the other side as not pending anymore
|
||||||
# so the other side only has access to his new money when we are sure
|
# so the other side only has access to his new money when we are sure
|
||||||
# the payer has enough to deduct from
|
# the payer has enough to deduct from
|
||||||
async with db.connect() as conn:
|
async with db.connect() as conn:
|
||||||
await update_payment_status(
|
internal_payment.status = PaymentState.SUCCESS
|
||||||
checking_id=internal_checking_id,
|
await update_payment(internal_payment, conn=conn)
|
||||||
status=PaymentState.SUCCESS,
|
|
||||||
conn=conn,
|
|
||||||
)
|
|
||||||
await send_payment_notification(wallet, new_payment)
|
await send_payment_notification(wallet, new_payment)
|
||||||
|
|
||||||
# notify receiver asynchronously
|
# notify receiver asynchronously
|
||||||
from lnbits.tasks import internal_invoice_queue
|
from lnbits.tasks import internal_invoice_queue
|
||||||
|
|
||||||
logger.debug(f"enqueuing internal invoice {internal_checking_id}")
|
logger.debug(f"enqueuing internal invoice {internal_payment.checking_id}")
|
||||||
await internal_invoice_queue.put(internal_checking_id)
|
await internal_invoice_queue.put(internal_payment.checking_id)
|
||||||
else:
|
else:
|
||||||
fee_reserve_msat = fee_reserve(invoice.amount_msat, internal=False)
|
fee_reserve_msat = fee_reserve(invoice.amount_msat, internal=False)
|
||||||
service_fee_msat = service_fee(invoice.amount_msat, internal=False)
|
service_fee_msat = service_fee(invoice.amount_msat, internal=False)
|
||||||
logger.debug(f"backend: sending payment {temp_id}")
|
logger.debug(f"backend: sending payment {temp_id}")
|
||||||
# actually pay the external invoice
|
# actually pay the external invoice
|
||||||
funding_source = get_funding_source()
|
funding_source = get_funding_source()
|
||||||
payment: PaymentResponse = await funding_source.pay_invoice(
|
payment_response: PaymentResponse = await funding_source.pay_invoice(
|
||||||
payment_request, fee_reserve_msat
|
payment_request, fee_reserve_msat
|
||||||
)
|
)
|
||||||
|
|
||||||
if payment.checking_id and payment.checking_id != temp_id:
|
if payment_response.checking_id and payment_response.checking_id != temp_id:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"backend sent unexpected checking_id (expected: {temp_id} got:"
|
f"backend sent unexpected checking_id (expected: {temp_id} got:"
|
||||||
f" {payment.checking_id})"
|
f" {payment_response.checking_id})"
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.debug(f"backend: pay_invoice finished {temp_id}, {payment}")
|
logger.debug(f"backend: pay_invoice finished {temp_id}, {payment_response}")
|
||||||
if payment.checking_id and payment.ok is not False:
|
if payment_response.checking_id and payment_response.ok is not False:
|
||||||
# payment.ok can be True (paid) or None (pending)!
|
# payment.ok can be True (paid) or None (pending)!
|
||||||
logger.debug(f"updating payment {temp_id}")
|
logger.debug(f"updating payment {temp_id}")
|
||||||
async with db.connect() as conn:
|
async with db.connect() as conn:
|
||||||
await update_payment_details(
|
payment = await get_payment(temp_id, conn=conn)
|
||||||
checking_id=temp_id,
|
# new checking id
|
||||||
status=(
|
payment.checking_id = payment_response.checking_id
|
||||||
PaymentState.SUCCESS
|
payment.status = (
|
||||||
if payment.ok is True
|
PaymentState.SUCCESS
|
||||||
else PaymentState.PENDING
|
if payment_response.ok is True
|
||||||
),
|
else PaymentState.PENDING
|
||||||
fee=-(
|
|
||||||
abs(payment.fee_msat if payment.fee_msat else 0)
|
|
||||||
+ abs(service_fee_msat)
|
|
||||||
),
|
|
||||||
preimage=payment.preimage,
|
|
||||||
new_checking_id=payment.checking_id,
|
|
||||||
conn=conn,
|
|
||||||
)
|
)
|
||||||
|
payment.fee = -(
|
||||||
|
abs(payment_response.fee_msat or 0) + abs(service_fee_msat)
|
||||||
|
)
|
||||||
|
payment.preimage = payment_response.preimage
|
||||||
|
await update_payment(payment, conn=conn)
|
||||||
wallet = await get_wallet(wallet_id, conn=conn)
|
wallet = await get_wallet(wallet_id, conn=conn)
|
||||||
updated = await get_wallet_payment(
|
if wallet:
|
||||||
wallet_id, payment.checking_id, conn=conn
|
await send_payment_notification(wallet, payment)
|
||||||
)
|
logger.success(f"payment successful {payment_response.checking_id}")
|
||||||
if wallet and updated:
|
elif payment_response.checking_id is None and payment_response.ok is False:
|
||||||
await send_payment_notification(wallet, updated)
|
|
||||||
logger.success(f"payment successful {payment.checking_id}")
|
|
||||||
elif payment.checking_id is None and payment.ok is False:
|
|
||||||
# payment failed
|
# payment failed
|
||||||
logger.debug(f"payment failed {temp_id}, {payment.error_message}")
|
logger.debug(f"payment failed {temp_id}, {payment_response.error_message}")
|
||||||
async with db.connect() as conn:
|
async with db.connect() as conn:
|
||||||
await update_payment_status(
|
payment = await get_payment(temp_id, conn=conn)
|
||||||
checking_id=temp_id,
|
payment.status = PaymentState.FAILED
|
||||||
status=PaymentState.FAILED,
|
await update_payment(payment, conn=conn)
|
||||||
conn=conn,
|
|
||||||
)
|
|
||||||
raise PaymentError(
|
raise PaymentError(
|
||||||
f"Payment failed: {payment.error_message}"
|
f"Payment failed: {payment_response.error_message}"
|
||||||
or "Payment failed, but backend didn't give us an error message.",
|
or "Payment failed, but backend didn't give us an error message.",
|
||||||
status="failed",
|
status="failed",
|
||||||
)
|
)
|
||||||
@@ -420,9 +409,8 @@ async def _create_external_payment(
|
|||||||
status = await old_payment.check_status()
|
status = await old_payment.check_status()
|
||||||
if status.success:
|
if status.success:
|
||||||
# payment was successful on the fundingsource
|
# payment was successful on the fundingsource
|
||||||
await update_payment_status(
|
old_payment.status = PaymentState.SUCCESS
|
||||||
checking_id=temp_id, status=PaymentState.SUCCESS, conn=conn
|
await update_payment(old_payment, conn=conn)
|
||||||
)
|
|
||||||
raise PaymentError(
|
raise PaymentError(
|
||||||
"Failed payment was already paid on the fundingsource.",
|
"Failed payment was already paid on the fundingsource.",
|
||||||
status="success",
|
status="success",
|
||||||
@@ -454,11 +442,11 @@ async def _create_external_payment(
|
|||||||
def _check_wallet_balance(
|
def _check_wallet_balance(
|
||||||
wallet: Wallet,
|
wallet: Wallet,
|
||||||
fee_reserve_total_msat: int,
|
fee_reserve_total_msat: int,
|
||||||
internal_checking_id: Optional[str] = None,
|
internal_payment: Optional[Payment] = None,
|
||||||
):
|
):
|
||||||
if wallet.balance_msat < 0:
|
if wallet.balance_msat < 0:
|
||||||
logger.debug("balance is too low, deleting temporary payment")
|
logger.debug("balance is too low, deleting temporary payment")
|
||||||
if not internal_checking_id and wallet.balance_msat > -fee_reserve_total_msat:
|
if not internal_payment and wallet.balance_msat > -fee_reserve_total_msat:
|
||||||
raise PaymentError(
|
raise PaymentError(
|
||||||
f"You must reserve at least ({round(fee_reserve_total_msat/1000)}"
|
f"You must reserve at least ({round(fee_reserve_total_msat/1000)}"
|
||||||
" sat) to cover potential routing fees.",
|
" sat) to cover potential routing fees.",
|
||||||
@@ -717,22 +705,23 @@ async def send_payment_notification(wallet: Wallet, payment: Payment):
|
|||||||
|
|
||||||
|
|
||||||
async def update_wallet_balance(wallet_id: str, amount: int):
|
async def update_wallet_balance(wallet_id: str, amount: int):
|
||||||
payment_hash, _ = await create_invoice(
|
|
||||||
wallet_id=wallet_id,
|
|
||||||
amount=amount,
|
|
||||||
memo="Admin top up",
|
|
||||||
internal=True,
|
|
||||||
)
|
|
||||||
async with db.connect() as conn:
|
async with db.connect() as conn:
|
||||||
checking_id = await check_internal(payment_hash, conn=conn)
|
payment_hash, _ = await create_invoice(
|
||||||
assert checking_id, "newly created checking_id cannot be retrieved"
|
wallet_id=wallet_id,
|
||||||
await update_payment_status(
|
amount=amount,
|
||||||
checking_id=checking_id, status=PaymentState.SUCCESS, conn=conn
|
memo="Admin top up",
|
||||||
|
internal=True,
|
||||||
|
conn=conn,
|
||||||
)
|
)
|
||||||
|
internal_payment = await check_internal(payment_hash, conn=conn)
|
||||||
|
assert internal_payment, "newly created checking_id cannot be retrieved"
|
||||||
|
|
||||||
|
internal_payment.status = PaymentState.SUCCESS
|
||||||
|
await update_payment(internal_payment, conn=conn)
|
||||||
# notify receiver asynchronously
|
# notify receiver asynchronously
|
||||||
from lnbits.tasks import internal_invoice_queue
|
from lnbits.tasks import internal_invoice_queue
|
||||||
|
|
||||||
await internal_invoice_queue.put(checking_id)
|
await internal_invoice_queue.put(internal_payment.checking_id)
|
||||||
|
|
||||||
|
|
||||||
async def check_admin_settings():
|
async def check_admin_settings():
|
||||||
@@ -813,22 +802,16 @@ def update_cached_settings(sets_dict: dict):
|
|||||||
|
|
||||||
|
|
||||||
async def init_admin_settings(super_user: Optional[str] = None) -> SuperSettings:
|
async def init_admin_settings(super_user: Optional[str] = None) -> SuperSettings:
|
||||||
async def new_account(account_id: str) -> Account:
|
|
||||||
now = datetime.now()
|
|
||||||
account = Account(
|
|
||||||
id=account_id,
|
|
||||||
extra=UserExtra(provider="env"),
|
|
||||||
created_at=now,
|
|
||||||
updated_at=now,
|
|
||||||
)
|
|
||||||
await create_account(account)
|
|
||||||
return account
|
|
||||||
|
|
||||||
account = None
|
account = None
|
||||||
if super_user:
|
if super_user:
|
||||||
account = await get_account(super_user)
|
account = await get_account(super_user)
|
||||||
if not account:
|
if not account:
|
||||||
account = await new_account(super_user or uuid4().hex)
|
account_id = super_user or uuid4().hex
|
||||||
|
account = Account(
|
||||||
|
id=account_id,
|
||||||
|
extra=UserExtra(provider="env"),
|
||||||
|
)
|
||||||
|
await create_account(account)
|
||||||
await create_wallet(user_id=account.id)
|
await create_wallet(user_id=account.id)
|
||||||
|
|
||||||
editable_settings = EditableSettings.from_dict(settings.dict())
|
editable_settings = EditableSettings.from_dict(settings.dict())
|
||||||
@@ -924,12 +907,8 @@ async def update_pending_payments(wallet_id: str):
|
|||||||
for payment in pending_payments:
|
for payment in pending_payments:
|
||||||
status = await payment.check_status()
|
status = await payment.check_status()
|
||||||
if status.failed:
|
if status.failed:
|
||||||
await update_payment_status(
|
payment.status = PaymentState.FAILED
|
||||||
checking_id=payment.checking_id,
|
await update_payment(payment)
|
||||||
status=PaymentState.FAILED,
|
|
||||||
)
|
|
||||||
elif status.success:
|
elif status.success:
|
||||||
await update_payment_status(
|
payment.status = PaymentState.SUCCESS
|
||||||
checking_id=payment.checking_id,
|
await update_payment(payment)
|
||||||
status=PaymentState.SUCCESS,
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -621,8 +621,8 @@
|
|||||||
<div v-else>
|
<div v-else>
|
||||||
<q-responsive :ratio="1">
|
<q-responsive :ratio="1">
|
||||||
<qrcode-stream
|
<qrcode-stream
|
||||||
@decode="decodeQR"
|
@detect="decodeQR"
|
||||||
@init="onInitQR"
|
@camera-on="onInitQR"
|
||||||
class="rounded-borders"
|
class="rounded-borders"
|
||||||
></qrcode-stream>
|
></qrcode-stream>
|
||||||
</q-responsive>
|
</q-responsive>
|
||||||
@@ -645,8 +645,8 @@
|
|||||||
<q-card class="q-pa-lg q-pt-xl">
|
<q-card class="q-pa-lg q-pt-xl">
|
||||||
<div class="text-center q-mb-lg">
|
<div class="text-center q-mb-lg">
|
||||||
<qrcode-stream
|
<qrcode-stream
|
||||||
@decode="decodeQR"
|
@detect="decodeQR"
|
||||||
@init="onInitQR"
|
@camera-on="onInitQR"
|
||||||
class="rounded-borders"
|
class="rounded-borders"
|
||||||
></qrcode-stream>
|
></qrcode-stream>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ from ..crud import (
|
|||||||
create_user_extension,
|
create_user_extension,
|
||||||
delete_dbversion,
|
delete_dbversion,
|
||||||
drop_extension_db,
|
drop_extension_db,
|
||||||
get_dbversions,
|
get_db_version,
|
||||||
get_installed_extension,
|
get_installed_extension,
|
||||||
get_installed_extensions,
|
get_installed_extensions,
|
||||||
get_user_extension,
|
get_user_extension,
|
||||||
@@ -474,7 +474,7 @@ async def get_extension_release(org: str, repo: str, tag_name: str):
|
|||||||
)
|
)
|
||||||
async def delete_extension_db(ext_id: str):
|
async def delete_extension_db(ext_id: str):
|
||||||
try:
|
try:
|
||||||
db_version = (await get_dbversions()).get(ext_id, None)
|
db_version = await get_db_version(ext_id)
|
||||||
if not db_version:
|
if not db_version:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=HTTPStatus.BAD_REQUEST,
|
status_code=HTTPStatus.BAD_REQUEST,
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ from lnbits.wallets import get_funding_source
|
|||||||
from ...utils.exchange_rates import allowed_currencies, currencies
|
from ...utils.exchange_rates import allowed_currencies, currencies
|
||||||
from ..crud import (
|
from ..crud import (
|
||||||
create_wallet,
|
create_wallet,
|
||||||
get_dbversions,
|
get_db_versions,
|
||||||
get_installed_extensions,
|
get_installed_extensions,
|
||||||
get_user,
|
get_user_by_id,
|
||||||
get_wallet,
|
get_wallet,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -104,7 +104,8 @@ async def extensions(request: Request, user: User = Depends(check_user_exists)):
|
|||||||
|
|
||||||
all_ext_ids = [ext.code for ext in Extension.get_valid_extensions()]
|
all_ext_ids = [ext.code for ext in Extension.get_valid_extensions()]
|
||||||
inactive_extensions = [e.id for e in await get_installed_extensions(active=False)]
|
inactive_extensions = [e.id for e in await get_installed_extensions(active=False)]
|
||||||
db_version = await get_dbversions()
|
db_versions = await get_db_versions()
|
||||||
|
|
||||||
extensions = [
|
extensions = [
|
||||||
{
|
{
|
||||||
"id": ext.id,
|
"id": ext.id,
|
||||||
@@ -115,7 +116,9 @@ async def extensions(request: Request, user: User = Depends(check_user_exists)):
|
|||||||
"isFeatured": ext.meta.featured if ext.meta else False,
|
"isFeatured": ext.meta.featured if ext.meta else False,
|
||||||
"dependencies": ext.meta.dependencies if ext.meta else "",
|
"dependencies": ext.meta.dependencies if ext.meta else "",
|
||||||
"isInstalled": ext.id in installed_exts_ids,
|
"isInstalled": ext.id in installed_exts_ids,
|
||||||
"hasDatabaseTables": ext.id in db_version,
|
"hasDatabaseTables": next(
|
||||||
|
(True for version in db_versions if version.db == ext.id), False
|
||||||
|
),
|
||||||
"isAvailable": ext.id in all_ext_ids,
|
"isAvailable": ext.id in all_ext_ids,
|
||||||
"isAdminOnly": ext.id in settings.lnbits_admin_extensions,
|
"isAdminOnly": ext.id in settings.lnbits_admin_extensions,
|
||||||
"isActive": ext.id not in inactive_extensions,
|
"isActive": ext.id not in inactive_extensions,
|
||||||
@@ -229,7 +232,7 @@ async def service_worker(request: Request):
|
|||||||
@generic_router.get("/manifest/{usr}.webmanifest")
|
@generic_router.get("/manifest/{usr}.webmanifest")
|
||||||
async def manifest(request: Request, usr: str):
|
async def manifest(request: Request, usr: str):
|
||||||
host = urlparse(str(request.url)).netloc
|
host = urlparse(str(request.url)).netloc
|
||||||
user = await get_user(usr)
|
user = await get_user_by_id(usr)
|
||||||
if not user:
|
if not user:
|
||||||
raise HTTPException(status_code=HTTPStatus.NOT_FOUND)
|
raise HTTPException(status_code=HTTPStatus.NOT_FOUND)
|
||||||
return {
|
return {
|
||||||
|
|||||||
+3
-1
@@ -175,7 +175,9 @@ class Connection(Compat):
|
|||||||
return dict_to_model(row, model)
|
return dict_to_model(row, model)
|
||||||
return row
|
return row
|
||||||
|
|
||||||
async def update(self, table_name: str, model: BaseModel, where: str = "id = :id"):
|
async def update(
|
||||||
|
self, table_name: str, model: BaseModel, where: str = "WHERE id = :id"
|
||||||
|
):
|
||||||
await self.conn.execute(
|
await self.conn.execute(
|
||||||
text(update_query(table_name, model, where)), model_to_dict(model)
|
text(update_query(table_name, model, where)), model_to_dict(model)
|
||||||
)
|
)
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+2
-2
File diff suppressed because one or more lines are too long
@@ -526,10 +526,6 @@ video {
|
|||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.q-card--dark, .q-date--dark {
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.q-card code {
|
.q-card code {
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -278,7 +278,7 @@ window.LNbits = {
|
|||||||
preimage: data.preimage,
|
preimage: data.preimage,
|
||||||
payment_hash: data.payment_hash,
|
payment_hash: data.payment_hash,
|
||||||
expiry: data.expiry,
|
expiry: data.expiry,
|
||||||
extra: data.extra ? JSON.parse(data.extra) : {},
|
extra: data.extra ?? {},
|
||||||
wallet_id: data.wallet_id,
|
wallet_id: data.wallet_id,
|
||||||
webhook: data.webhook,
|
webhook: data.webhook,
|
||||||
webhook_status: data.webhook_status,
|
webhook_status: data.webhook_status,
|
||||||
|
|||||||
@@ -409,7 +409,7 @@ window.app.component('lnbits-dynamic-fields', {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
formData: null,
|
formData: null,
|
||||||
rules: [val => !!val || 'Field is required'],
|
rules: [val => !!val || 'Field is required']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -428,7 +428,7 @@ window.app.component('lnbits-dynamic-fields', {
|
|||||||
},
|
},
|
||||||
handleValueChanged() {
|
handleValueChanged() {
|
||||||
this.$emit('update:model-value', this.formData)
|
this.$emit('update:model-value', this.formData)
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.formData = this.buildData(this.options, this.modelValue)
|
this.formData = this.buildData(this.options, this.modelValue)
|
||||||
@@ -450,7 +450,7 @@ window.app.component('lnbits-dynamic-chips', {
|
|||||||
if (!this.chip) return
|
if (!this.chip) return
|
||||||
this.chips.push(this.chip)
|
this.chips.push(this.chip)
|
||||||
this.chip = ''
|
this.chip = ''
|
||||||
this.modelValue = this.chips.join(',')
|
this.$emit('update:model-value', this.chips.join(','))
|
||||||
},
|
},
|
||||||
removeChip(index) {
|
removeChip(index) {
|
||||||
this.chips.splice(index, 1)
|
this.chips.splice(index, 1)
|
||||||
@@ -464,7 +464,6 @@ window.app.component('lnbits-dynamic-chips', {
|
|||||||
this.chips = [...this.modelValue]
|
this.chips = [...this.modelValue]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
window.app.component('lnbits-update-balance', {
|
window.app.component('lnbits-update-balance', {
|
||||||
|
|||||||
@@ -257,7 +257,7 @@ window.app = Vue.createApp({
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
decodeQR: function (res) {
|
decodeQR: function (res) {
|
||||||
this.parse.data.request = res
|
this.parse.data.request = res[0].rawValue
|
||||||
this.decodeRequest()
|
this.decodeRequest()
|
||||||
this.parse.camera.show = false
|
this.parse.camera.show = false
|
||||||
},
|
},
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
+11
-19
@@ -20,8 +20,7 @@ from lnbits.core.crud import (
|
|||||||
delete_webpush_subscriptions,
|
delete_webpush_subscriptions,
|
||||||
get_payments,
|
get_payments,
|
||||||
get_standalone_payment,
|
get_standalone_payment,
|
||||||
update_payment_details,
|
update_payment,
|
||||||
update_payment_status,
|
|
||||||
)
|
)
|
||||||
from lnbits.core.models import Payment, PaymentState
|
from lnbits.core.models import Payment, PaymentState
|
||||||
from lnbits.settings import settings
|
from lnbits.settings import settings
|
||||||
@@ -181,17 +180,14 @@ async def check_pending_payments():
|
|||||||
status = await payment.check_status()
|
status = await payment.check_status()
|
||||||
prefix = f"payment ({i+1} / {count})"
|
prefix = f"payment ({i+1} / {count})"
|
||||||
if status.failed:
|
if status.failed:
|
||||||
await update_payment_status(
|
payment.status = PaymentState.FAILED
|
||||||
payment.checking_id, status=PaymentState.FAILED
|
await update_payment(payment)
|
||||||
)
|
|
||||||
logger.debug(f"{prefix} failed {payment.checking_id}")
|
logger.debug(f"{prefix} failed {payment.checking_id}")
|
||||||
elif status.success:
|
elif status.success:
|
||||||
await update_payment_details(
|
payment.fee = status.fee_msat or 0
|
||||||
checking_id=payment.checking_id,
|
payment.preimage = status.preimage
|
||||||
fee=status.fee_msat,
|
payment.status = PaymentState.SUCCESS
|
||||||
preimage=status.preimage,
|
await update_payment(payment)
|
||||||
status=PaymentState.SUCCESS,
|
|
||||||
)
|
|
||||||
logger.debug(f"{prefix} success {payment.checking_id}")
|
logger.debug(f"{prefix} success {payment.checking_id}")
|
||||||
else:
|
else:
|
||||||
logger.debug(f"{prefix} pending {payment.checking_id}")
|
logger.debug(f"{prefix} pending {payment.checking_id}")
|
||||||
@@ -211,14 +207,10 @@ async def invoice_callback_dispatcher(checking_id: str, is_internal: bool = Fals
|
|||||||
payment = await get_standalone_payment(checking_id, incoming=True)
|
payment = await get_standalone_payment(checking_id, incoming=True)
|
||||||
if payment and payment.is_in:
|
if payment and payment.is_in:
|
||||||
status = await payment.check_status()
|
status = await payment.check_status()
|
||||||
await update_payment_details(
|
payment.fee = status.fee_msat or 0
|
||||||
checking_id=payment.checking_id,
|
payment.preimage = status.preimage
|
||||||
fee=status.fee_msat,
|
payment.status = PaymentState.SUCCESS
|
||||||
preimage=status.preimage,
|
await update_payment(payment)
|
||||||
status=PaymentState.SUCCESS,
|
|
||||||
)
|
|
||||||
payment = await get_standalone_payment(checking_id, incoming=True)
|
|
||||||
assert payment, "updated payment not found"
|
|
||||||
internal = "internal" if is_internal else ""
|
internal = "internal" if is_internal else ""
|
||||||
logger.success(f"{internal} invoice {checking_id} settled")
|
logger.success(f"{internal} invoice {checking_id} settled")
|
||||||
for name, send_chan in invoice_listeners.items():
|
for name, send_chan in invoice_listeners.items():
|
||||||
|
|||||||
@@ -393,7 +393,7 @@
|
|||||||
></q-input>
|
></q-input>
|
||||||
<div v-else-if="o.type === 'chips'">
|
<div v-else-if="o.type === 'chips'">
|
||||||
<lnbits-dynamic-chips
|
<lnbits-dynamic-chips
|
||||||
:model-value="formData[o.name]"
|
v-model="formData[o.name]"
|
||||||
@update:model-value="handleValueChanged"
|
@update:model-value="handleValueChanged"
|
||||||
></lnbits-dynamic-chips>
|
></lnbits-dynamic-chips>
|
||||||
</div>
|
</div>
|
||||||
@@ -489,8 +489,15 @@
|
|||||||
|
|
||||||
<template id="lnbits-qrcode">
|
<template id="lnbits-qrcode">
|
||||||
<div class="qrcode__wrapper">
|
<div class="qrcode__wrapper">
|
||||||
<qrcode-vue :value="value" size="350" class="rounded-borders"></qrcode-vue>
|
<qrcode-vue
|
||||||
<img class="qrcode__image" :src="logo" alt="..." />
|
:value="value"
|
||||||
|
level="Q"
|
||||||
|
render-as="svg"
|
||||||
|
margin="1"
|
||||||
|
size="350"
|
||||||
|
class="rounded-borders"
|
||||||
|
></qrcode-vue>
|
||||||
|
<img class="qrcode__image" :src="logo" alt="qrcode icon" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
Generated
+4
-3
@@ -1320,9 +1320,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vue-qrcode-reader": {
|
"node_modules/vue-qrcode-reader": {
|
||||||
"version": "5.5.10",
|
"version": "5.5.11",
|
||||||
"resolved": "https://registry.npmjs.org/vue-qrcode-reader/-/vue-qrcode-reader-5.5.10.tgz",
|
"resolved": "https://registry.npmjs.org/vue-qrcode-reader/-/vue-qrcode-reader-5.5.11.tgz",
|
||||||
"integrity": "sha512-lj83FKqRyvo0VLMu49wrLsaHueonfXcwyX9r/GDw0y+myOY5xTfsl75hjBgmmByAxzFSlCPI+CGA9FxYVtRAFQ==",
|
"integrity": "sha512-Ec/bVML1jgxSX+usbgdcXGhOFEFo4EzApCO2CNT1YK0Dcb0Mp7ASygz78RJJs22SU2oI7vz9iJDyr4ucSDTvjQ==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"barcode-detector": "2.2.2",
|
"barcode-detector": "2.2.2",
|
||||||
"webrtc-adapter": "8.2.3"
|
"webrtc-adapter": "8.2.3"
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "lnbits"
|
name = "lnbits"
|
||||||
version = "1.0.0-rc3"
|
version = "1.0.0-rc4"
|
||||||
description = "LNbits, free and open-source Lightning wallet and accounts system."
|
description = "LNbits, free and open-source Lightning wallet and accounts system."
|
||||||
authors = ["Alan Bits <alan@lnbits.com>"]
|
authors = ["Alan Bits <alan@lnbits.com>"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|||||||
+5
-2
@@ -20,8 +20,9 @@ from lnbits.core.crud import (
|
|||||||
delete_account,
|
delete_account,
|
||||||
get_account,
|
get_account,
|
||||||
get_account_by_username,
|
get_account_by_username,
|
||||||
|
get_payment,
|
||||||
get_user,
|
get_user,
|
||||||
update_payment_status,
|
update_payment,
|
||||||
)
|
)
|
||||||
from lnbits.core.models import Account, CreateInvoice, PaymentState, User
|
from lnbits.core.models import Account, CreateInvoice, PaymentState, User
|
||||||
from lnbits.core.services import create_user_account, update_wallet_balance
|
from lnbits.core.services import create_user_account, update_wallet_balance
|
||||||
@@ -250,7 +251,9 @@ async def fake_payments(client, adminkey_headers_from):
|
|||||||
assert response.is_success
|
assert response.is_success
|
||||||
data = response.json()
|
data = response.json()
|
||||||
assert data["checking_id"]
|
assert data["checking_id"]
|
||||||
await update_payment_status(data["checking_id"], status=PaymentState.SUCCESS)
|
payment = await get_payment(data["checking_id"])
|
||||||
|
payment.status = PaymentState.SUCCESS
|
||||||
|
await update_payment(payment)
|
||||||
|
|
||||||
params = {"time[ge]": ts, "time[le]": time()}
|
params = {"time[ge]": ts, "time[le]": time()}
|
||||||
return fake_data, params
|
return fake_data, params
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import hashlib
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from lnbits import bolt11
|
from lnbits import bolt11
|
||||||
from lnbits.core.crud import get_standalone_payment, update_payment_details
|
from lnbits.core.crud import get_standalone_payment, update_payment
|
||||||
from lnbits.core.models import CreateInvoice, Payment, PaymentState
|
from lnbits.core.models import CreateInvoice, Payment, PaymentState
|
||||||
from lnbits.core.services import fee_reserve_total, get_balance_delta
|
from lnbits.core.services import fee_reserve_total, get_balance_delta
|
||||||
from lnbits.tasks import create_task, wait_for_paid_invoices
|
from lnbits.tasks import create_task, wait_for_paid_invoices
|
||||||
@@ -303,7 +303,8 @@ async def test_receive_real_invoice_set_pending_and_check_state(
|
|||||||
assert payment
|
assert payment
|
||||||
|
|
||||||
# set the incoming invoice to pending
|
# set the incoming invoice to pending
|
||||||
await update_payment_details(payment.checking_id, status=PaymentState.PENDING)
|
payment.status = PaymentState.PENDING
|
||||||
|
await update_payment(payment)
|
||||||
|
|
||||||
payment_pending = await get_standalone_payment(
|
payment_pending = await get_standalone_payment(
|
||||||
invoice["payment_hash"], incoming=True
|
invoice["payment_hash"], incoming=True
|
||||||
|
|||||||
Reference in New Issue
Block a user