Merge branch 'main' into gerty
This commit is contained in:
commit
8f00e35d1e
12
.env.example
12
.env.example
|
@ -10,13 +10,19 @@ DEBUG=false
|
|||
LNBITS_ALLOWED_USERS=""
|
||||
LNBITS_ADMIN_USERS=""
|
||||
# Extensions only admin can access
|
||||
LNBITS_ADMIN_EXTENSIONS="ngrok"
|
||||
LNBITS_ADMIN_EXTENSIONS="ngrok, admin"
|
||||
# Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS if available
|
||||
LNBITS_ADMIN_UI=false
|
||||
|
||||
# Restricts access, User IDs seperated by comma
|
||||
LNBITS_ALLOWED_USERS=""
|
||||
|
||||
LNBITS_DEFAULT_WALLET_NAME="LNbits wallet"
|
||||
|
||||
# Ad space description
|
||||
# LNBITS_AD_SPACE_TITLE="Supported by"
|
||||
# csv ad space, format "<url>;<img-light>;<img-dark>, <url>;<img-light>;<img-dark>", extensions can choose to honor
|
||||
# LNBITS_AD_SPACE=""
|
||||
# LNBITS_AD_SPACE="https://shop.lnbits.com/;https://raw.githubusercontent.com/lnbits/lnbits/main/lnbits/static/images/lnbits-shop-light.png;https://raw.githubusercontent.com/lnbits/lnbits/main/lnbits/static/images/lnbits-shop-dark.png"
|
||||
|
||||
# Hides wallet api, extensions can choose to honor
|
||||
LNBITS_HIDE_API=false
|
||||
|
@ -105,6 +111,6 @@ LNTIPS_API_KEY=LNTIPS_ADMIN_KEY
|
|||
LNTIPS_API_ENDPOINT=https://ln.tips
|
||||
|
||||
# Cashu Mint
|
||||
# Use a long-enough random (!) private key.
|
||||
# Use a long-enough random (!) private key.
|
||||
# Once set, you cannot change this key as for now.
|
||||
CASHU_PRIVATE_KEY="SuperSecretPrivateKey"
|
||||
|
|
|
@ -13,7 +13,7 @@ RUN mkdir -p lnbits/data
|
|||
COPY . .
|
||||
|
||||
RUN poetry config virtualenvs.create false
|
||||
RUN poetry install --no-dev --no-root
|
||||
RUN poetry install --only main --no-root
|
||||
RUN poetry run python build.py
|
||||
|
||||
ENV LNBITS_PORT="5000"
|
||||
|
|
|
@ -1,38 +1,3 @@
|
|||
import asyncio
|
||||
|
||||
import uvloop
|
||||
from loguru import logger
|
||||
from starlette.requests import Request
|
||||
|
||||
from .commands import migrate_databases
|
||||
from .settings import (
|
||||
DEBUG,
|
||||
HOST,
|
||||
LNBITS_COMMIT,
|
||||
LNBITS_DATA_FOLDER,
|
||||
LNBITS_DATABASE_URL,
|
||||
LNBITS_SITE_TITLE,
|
||||
PORT,
|
||||
WALLET,
|
||||
)
|
||||
|
||||
uvloop.install()
|
||||
|
||||
asyncio.create_task(migrate_databases())
|
||||
|
||||
from .app import create_app
|
||||
|
||||
app = create_app()
|
||||
|
||||
logger.info("Starting LNbits")
|
||||
logger.info(f"Host: {HOST}")
|
||||
logger.info(f"Port: {PORT}")
|
||||
logger.info(f"Debug: {DEBUG}")
|
||||
logger.info(f"Site title: {LNBITS_SITE_TITLE}")
|
||||
logger.info(f"Funding source: {WALLET.__class__.__name__}")
|
||||
logger.info(
|
||||
f"Database: {'PostgreSQL' if LNBITS_DATABASE_URL and LNBITS_DATABASE_URL.startswith('postgres://') else 'CockroachDB' if LNBITS_DATABASE_URL and LNBITS_DATABASE_URL.startswith('cockroachdb://') else 'SQLite'}"
|
||||
)
|
||||
logger.info(f"Data folder: {LNBITS_DATA_FOLDER}")
|
||||
logger.info(f"Git version: {LNBITS_COMMIT}")
|
||||
# logger.info(f"Service fee: {SERVICE_FEE}")
|
||||
|
|
128
lnbits/app.py
128
lnbits/app.py
|
@ -4,7 +4,6 @@ import logging
|
|||
import signal
|
||||
import sys
|
||||
import traceback
|
||||
import warnings
|
||||
from http import HTTPStatus
|
||||
|
||||
from fastapi import FastAPI, Request
|
||||
|
@ -15,10 +14,12 @@ from fastapi.responses import JSONResponse
|
|||
from fastapi.staticfiles import StaticFiles
|
||||
from loguru import logger
|
||||
|
||||
import lnbits.settings
|
||||
from lnbits.core.tasks import register_task_listeners
|
||||
from lnbits.settings import get_wallet_class, set_wallet_class, settings
|
||||
|
||||
from .commands import migrate_databases
|
||||
from .core import core_app
|
||||
from .core.services import check_admin_settings
|
||||
from .core.views.generic import core_html_routes
|
||||
from .helpers import (
|
||||
get_css_vendored,
|
||||
|
@ -28,7 +29,6 @@ from .helpers import (
|
|||
url_for_vendored,
|
||||
)
|
||||
from .requestvars import g
|
||||
from .settings import WALLET
|
||||
from .tasks import (
|
||||
catch_everything_and_restart,
|
||||
check_pending_payments,
|
||||
|
@ -38,10 +38,8 @@ from .tasks import (
|
|||
)
|
||||
|
||||
|
||||
def create_app(config_object="lnbits.settings") -> FastAPI:
|
||||
"""Create application factory.
|
||||
:param config_object: The configuration object to use.
|
||||
"""
|
||||
def create_app() -> FastAPI:
|
||||
|
||||
configure_logger()
|
||||
|
||||
app = FastAPI(
|
||||
|
@ -49,9 +47,10 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
|
|||
description="API for LNbits, the free and open source bitcoin wallet and accounts system with plugins.",
|
||||
license_info={
|
||||
"name": "MIT License",
|
||||
"url": "https://raw.githubusercontent.com/lnbits/lnbits-legend/main/LICENSE",
|
||||
"url": "https://raw.githubusercontent.com/lnbits/lnbits/main/LICENSE",
|
||||
},
|
||||
)
|
||||
|
||||
app.mount("/static", StaticFiles(packages=[("lnbits", "static")]), name="static")
|
||||
app.mount(
|
||||
"/core/static",
|
||||
|
@ -59,18 +58,15 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
|
|||
name="core_static",
|
||||
)
|
||||
|
||||
origins = ["*"]
|
||||
g().base_url = f"http://{settings.host}:{settings.port}"
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware, allow_origins=origins, allow_methods=["*"], allow_headers=["*"]
|
||||
CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"]
|
||||
)
|
||||
|
||||
g().config = lnbits.settings
|
||||
g().base_url = f"http://{lnbits.settings.HOST}:{lnbits.settings.PORT}"
|
||||
|
||||
app.add_middleware(GZipMiddleware, minimum_size=1000)
|
||||
|
||||
check_funding_source(app)
|
||||
register_startup(app)
|
||||
register_assets(app)
|
||||
register_routes(app)
|
||||
register_async_tasks(app)
|
||||
|
@ -79,33 +75,34 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
|
|||
return app
|
||||
|
||||
|
||||
def check_funding_source(app: FastAPI) -> None:
|
||||
@app.on_event("startup")
|
||||
async def check_wallet_status():
|
||||
original_sigint_handler = signal.getsignal(signal.SIGINT)
|
||||
async def check_funding_source() -> None:
|
||||
|
||||
def signal_handler(signal, frame):
|
||||
logger.debug(f"SIGINT received, terminating LNbits.")
|
||||
sys.exit(1)
|
||||
original_sigint_handler = signal.getsignal(signal.SIGINT)
|
||||
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
while True:
|
||||
try:
|
||||
error_message, balance = await WALLET.status()
|
||||
if not error_message:
|
||||
break
|
||||
logger.error(
|
||||
f"The backend for {WALLET.__class__.__name__} isn't working properly: '{error_message}'",
|
||||
RuntimeWarning,
|
||||
)
|
||||
except:
|
||||
pass
|
||||
logger.info("Retrying connection to backend in 5 seconds...")
|
||||
await asyncio.sleep(5)
|
||||
signal.signal(signal.SIGINT, original_sigint_handler)
|
||||
logger.success(
|
||||
f"✔️ Backend {WALLET.__class__.__name__} connected and with a balance of {balance} msat."
|
||||
)
|
||||
def signal_handler(signal, frame):
|
||||
logger.debug(f"SIGINT received, terminating LNbits.")
|
||||
sys.exit(1)
|
||||
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
|
||||
WALLET = get_wallet_class()
|
||||
while True:
|
||||
try:
|
||||
error_message, balance = await WALLET.status()
|
||||
if not error_message:
|
||||
break
|
||||
logger.error(
|
||||
f"The backend for {WALLET.__class__.__name__} isn't working properly: '{error_message}'",
|
||||
RuntimeWarning,
|
||||
)
|
||||
except:
|
||||
pass
|
||||
logger.info("Retrying connection to backend in 5 seconds...")
|
||||
await asyncio.sleep(5)
|
||||
signal.signal(signal.SIGINT, original_sigint_handler)
|
||||
logger.info(
|
||||
f"✔️ Backend {WALLET.__class__.__name__} connected and with a balance of {balance} msat."
|
||||
)
|
||||
|
||||
|
||||
def register_routes(app: FastAPI) -> None:
|
||||
|
@ -136,12 +133,59 @@ def register_routes(app: FastAPI) -> None:
|
|||
)
|
||||
|
||||
|
||||
def register_startup(app: FastAPI):
|
||||
@app.on_event("startup")
|
||||
async def lnbits_startup():
|
||||
|
||||
try:
|
||||
# 1. wait till migration is done
|
||||
await migrate_databases()
|
||||
|
||||
# 2. setup admin settings
|
||||
await check_admin_settings()
|
||||
|
||||
log_server_info()
|
||||
|
||||
# 3. initialize WALLET
|
||||
set_wallet_class()
|
||||
|
||||
# 4. initialize funding source
|
||||
await check_funding_source()
|
||||
except Exception as e:
|
||||
logger.error(str(e))
|
||||
raise ImportError("Failed to run 'startup' event.")
|
||||
|
||||
|
||||
def log_server_info():
|
||||
logger.info("Starting LNbits")
|
||||
logger.info(f"Host: {settings.host}")
|
||||
logger.info(f"Port: {settings.port}")
|
||||
logger.info(f"Debug: {settings.debug}")
|
||||
logger.info(f"Site title: {settings.lnbits_site_title}")
|
||||
logger.info(f"Funding source: {settings.lnbits_backend_wallet_class}")
|
||||
logger.info(f"Data folder: {settings.lnbits_data_folder}")
|
||||
logger.info(f"Git version: {settings.lnbits_commit}")
|
||||
logger.info(f"Database: {get_db_vendor_name()}")
|
||||
logger.info(f"Service fee: {settings.lnbits_service_fee}")
|
||||
|
||||
|
||||
def get_db_vendor_name():
|
||||
db_url = settings.lnbits_database_url
|
||||
return (
|
||||
"PostgreSQL"
|
||||
if db_url and db_url.startswith("postgres://")
|
||||
else "CockroachDB"
|
||||
if db_url and db_url.startswith("cockroachdb://")
|
||||
else "SQLite"
|
||||
)
|
||||
|
||||
|
||||
def register_assets(app: FastAPI):
|
||||
"""Serve each vendored asset separately or a bundle."""
|
||||
|
||||
@app.on_event("startup")
|
||||
async def vendored_assets_variable():
|
||||
if g().config.DEBUG:
|
||||
if settings.debug:
|
||||
g().VENDORED_JS = map(url_for_vendored, get_js_vendored())
|
||||
g().VENDORED_CSS = map(url_for_vendored, get_css_vendored())
|
||||
else:
|
||||
|
@ -240,7 +284,7 @@ def register_exception_handlers(app: FastAPI):
|
|||
|
||||
def configure_logger() -> None:
|
||||
logger.remove()
|
||||
log_level: str = "DEBUG" if lnbits.settings.DEBUG else "INFO"
|
||||
log_level: str = "DEBUG" if settings.debug else "INFO"
|
||||
formatter = Formatter()
|
||||
logger.add(sys.stderr, level=log_level, format=formatter.format)
|
||||
|
||||
|
@ -252,7 +296,7 @@ class Formatter:
|
|||
def __init__(self):
|
||||
self.padding = 0
|
||||
self.minimal_fmt: str = "<green>{time:YYYY-MM-DD HH:mm:ss.SS}</green> | <level>{level}</level> | <level>{message}</level>\n"
|
||||
if lnbits.settings.DEBUG:
|
||||
if settings.debug:
|
||||
self.fmt: str = "<green>{time:YYYY-MM-DD HH:mm:ss.SS}</green> | <level>{level: <4}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> | <level>{message}</level>\n"
|
||||
else:
|
||||
self.fmt: str = self.minimal_fmt
|
||||
|
|
|
@ -7,6 +7,8 @@ import warnings
|
|||
import click
|
||||
from loguru import logger
|
||||
|
||||
from lnbits.settings import settings
|
||||
|
||||
from .core import db as core_db
|
||||
from .core import migrations as core_migrations
|
||||
from .db import COCKROACH, POSTGRES, SQLITE
|
||||
|
@ -16,7 +18,6 @@ from .helpers import (
|
|||
get_valid_extensions,
|
||||
url_for_vendored,
|
||||
)
|
||||
from .settings import LNBITS_PATH
|
||||
|
||||
|
||||
@click.command("migrate")
|
||||
|
@ -35,15 +36,17 @@ def transpile_scss():
|
|||
warnings.simplefilter("ignore")
|
||||
from scss.compiler import compile_string # type: ignore
|
||||
|
||||
with open(os.path.join(LNBITS_PATH, "static/scss/base.scss")) as scss:
|
||||
with open(os.path.join(LNBITS_PATH, "static/css/base.css"), "w") as css:
|
||||
with open(os.path.join(settings.lnbits_path, "static/scss/base.scss")) as scss:
|
||||
with open(
|
||||
os.path.join(settings.lnbits_path, "static/css/base.css"), "w"
|
||||
) as css:
|
||||
css.write(compile_string(scss.read()))
|
||||
|
||||
|
||||
def bundle_vendored():
|
||||
for getfiles, outputpath in [
|
||||
(get_js_vendored, os.path.join(LNBITS_PATH, "static/bundle.js")),
|
||||
(get_css_vendored, os.path.join(LNBITS_PATH, "static/bundle.css")),
|
||||
(get_js_vendored, os.path.join(settings.lnbits_path, "static/bundle.js")),
|
||||
(get_css_vendored, os.path.join(settings.lnbits_path, "static/bundle.css")),
|
||||
]:
|
||||
output = ""
|
||||
for path in getfiles():
|
||||
|
|
|
@ -6,6 +6,7 @@ db = Database("database")
|
|||
|
||||
core_app: APIRouter = APIRouter()
|
||||
|
||||
from .views.admin_api import * # noqa
|
||||
from .views.api import * # noqa
|
||||
from .views.generic import * # noqa
|
||||
from .views.public_api import * # noqa
|
||||
|
|
|
@ -4,11 +4,9 @@ from typing import Any, Dict, List, Optional
|
|||
from urllib.parse import urlparse
|
||||
from uuid import uuid4
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from lnbits import bolt11
|
||||
from lnbits.db import COCKROACH, POSTGRES, Connection
|
||||
from lnbits.settings import DEFAULT_WALLET_NAME, LNBITS_ADMIN_USERS
|
||||
from lnbits.settings import AdminSettings, EditableSetings, SuperSettings, settings
|
||||
|
||||
from . import db
|
||||
from .models import BalanceCheck, Payment, User, Wallet
|
||||
|
@ -63,9 +61,8 @@ async def get_user(user_id: str, conn: Optional[Connection] = None) -> Optional[
|
|||
email=user["email"],
|
||||
extensions=[e[0] for e in extensions],
|
||||
wallets=[Wallet(**w) for w in wallets],
|
||||
admin=user["id"] in [x.strip() for x in LNBITS_ADMIN_USERS]
|
||||
if LNBITS_ADMIN_USERS
|
||||
else False,
|
||||
admin=user["id"] == settings.super_user
|
||||
or user["id"] in settings.lnbits_admin_users,
|
||||
)
|
||||
|
||||
|
||||
|
@ -99,7 +96,7 @@ async def create_wallet(
|
|||
""",
|
||||
(
|
||||
wallet_id,
|
||||
wallet_name or DEFAULT_WALLET_NAME,
|
||||
wallet_name or settings.lnbits_default_wallet_name,
|
||||
user_id,
|
||||
uuid4().hex,
|
||||
uuid4().hex,
|
||||
|
@ -232,8 +229,8 @@ async def get_wallet_payment(
|
|||
async def get_latest_payments_by_extension(ext_name: str, ext_id: str, limit: int = 5):
|
||||
rows = await db.fetchall(
|
||||
f"""
|
||||
SELECT * FROM apipayments
|
||||
WHERE pending = 'false'
|
||||
SELECT * FROM apipayments
|
||||
WHERE pending = 'false'
|
||||
AND extra LIKE ?
|
||||
AND extra LIKE ?
|
||||
ORDER BY time DESC LIMIT {limit}
|
||||
|
@ -550,3 +547,48 @@ async def get_balance_notify(
|
|||
(wallet_id,),
|
||||
)
|
||||
return row[0] if row else None
|
||||
|
||||
|
||||
# admin
|
||||
# --------
|
||||
|
||||
|
||||
async def get_super_settings() -> Optional[SuperSettings]:
|
||||
row = await db.fetchone("SELECT * FROM settings")
|
||||
if not row:
|
||||
return None
|
||||
editable_settings = json.loads(row["editable_settings"])
|
||||
return SuperSettings(**{"super_user": row["super_user"], **editable_settings})
|
||||
|
||||
|
||||
async def get_admin_settings(is_super_user: bool = False) -> Optional[AdminSettings]:
|
||||
sets = await get_super_settings()
|
||||
if not sets:
|
||||
return None
|
||||
row_dict = dict(sets)
|
||||
row_dict.pop("super_user")
|
||||
admin_settings = AdminSettings(
|
||||
super_user=is_super_user,
|
||||
lnbits_allowed_funding_sources=settings.lnbits_allowed_funding_sources,
|
||||
**row_dict,
|
||||
)
|
||||
return admin_settings
|
||||
|
||||
|
||||
async def delete_admin_settings():
|
||||
await db.execute("DELETE FROM settings")
|
||||
|
||||
|
||||
async def update_admin_settings(data: EditableSetings):
|
||||
await db.execute(f"UPDATE settings SET editable_settings = ?", (json.dumps(data),))
|
||||
|
||||
|
||||
async def update_super_user(super_user: str):
|
||||
await db.execute("UPDATE settings SET super_user = ?", (super_user,))
|
||||
return await get_super_settings()
|
||||
|
||||
|
||||
async def create_admin_settings(super_user: str, new_settings: dict):
|
||||
sql = f"INSERT INTO settings (super_user, editable_settings) VALUES (?, ?)"
|
||||
await db.execute(sql, (super_user, json.dumps(new_settings)))
|
||||
return await get_super_settings()
|
||||
|
|
|
@ -258,3 +258,14 @@ async def m007_set_invoice_expiries(db):
|
|||
# catching errors like this won't be necessary in anymore now that we
|
||||
# keep track of db versions so no migration ever runs twice.
|
||||
pass
|
||||
|
||||
|
||||
async def m008_create_admin_settings_table(db):
|
||||
await db.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS settings (
|
||||
super_user TEXT,
|
||||
editable_settings TEXT NOT NULL DEFAULT '{}'
|
||||
);
|
||||
"""
|
||||
)
|
||||
|
|
|
@ -7,13 +7,14 @@ from sqlite3 import Row
|
|||
from typing import Dict, List, NamedTuple, Optional
|
||||
|
||||
from ecdsa import SECP256k1, SigningKey # type: ignore
|
||||
from fastapi import Query
|
||||
from lnurl import encode as lnurl_encode # type: ignore
|
||||
from loguru import logger
|
||||
from pydantic import BaseModel
|
||||
from pydantic import BaseModel, Extra, validator
|
||||
|
||||
from lnbits.db import Connection
|
||||
from lnbits.helpers import url_for
|
||||
from lnbits.settings import WALLET
|
||||
from lnbits.settings import get_wallet_class
|
||||
from lnbits.wallets.base import PaymentStatus
|
||||
|
||||
|
||||
|
@ -65,6 +66,7 @@ class User(BaseModel):
|
|||
wallets: List[Wallet] = []
|
||||
password: Optional[str] = None
|
||||
admin: bool = False
|
||||
super_user: bool = False
|
||||
|
||||
@property
|
||||
def wallet_ids(self) -> List[str]:
|
||||
|
@ -171,6 +173,7 @@ class Payment(BaseModel):
|
|||
f"Checking {'outgoing' if self.is_out else 'incoming'} pending payment {self.checking_id}"
|
||||
)
|
||||
|
||||
WALLET = get_wallet_class()
|
||||
if self.is_out:
|
||||
status = await WALLET.get_payment_status(self.checking_id)
|
||||
else:
|
||||
|
|
|
@ -6,7 +6,7 @@ from typing import Dict, List, Optional, Tuple
|
|||
from urllib.parse import parse_qs, urlparse
|
||||
|
||||
import httpx
|
||||
from fastapi import Depends, WebSocket, WebSocketDisconnect
|
||||
from fastapi import Depends, WebSocket
|
||||
from lnurl import LnurlErrorResponse
|
||||
from lnurl import decode as decode_lnurl # type: ignore
|
||||
from loguru import logger
|
||||
|
@ -21,18 +21,31 @@ from lnbits.decorators import (
|
|||
)
|
||||
from lnbits.helpers import url_for, urlsafe_short_hash
|
||||
from lnbits.requestvars import g
|
||||
from lnbits.settings import FAKE_WALLET, RESERVE_FEE_MIN, RESERVE_FEE_PERCENT, WALLET
|
||||
from lnbits.settings import (
|
||||
FAKE_WALLET,
|
||||
EditableSetings,
|
||||
get_wallet_class,
|
||||
readonly_variables,
|
||||
send_admin_user_to_saas,
|
||||
settings,
|
||||
)
|
||||
from lnbits.wallets.base import PaymentResponse, PaymentStatus
|
||||
|
||||
from . import db
|
||||
from .crud import (
|
||||
check_internal,
|
||||
create_account,
|
||||
create_admin_settings,
|
||||
create_payment,
|
||||
create_wallet,
|
||||
delete_wallet_payment,
|
||||
get_account,
|
||||
get_super_settings,
|
||||
get_wallet,
|
||||
get_wallet_payment,
|
||||
update_payment_details,
|
||||
update_payment_status,
|
||||
update_super_user,
|
||||
)
|
||||
from .models import Payment
|
||||
|
||||
|
@ -65,7 +78,7 @@ async def create_invoice(
|
|||
invoice_memo = None if description_hash else memo
|
||||
|
||||
# use the fake wallet if the invoice is for internal use only
|
||||
wallet = FAKE_WALLET if internal else WALLET
|
||||
wallet = FAKE_WALLET if internal else get_wallet_class()
|
||||
|
||||
ok, checking_id, payment_request, error_message = await wallet.create_invoice(
|
||||
amount=amount,
|
||||
|
@ -193,6 +206,7 @@ async def pay_invoice(
|
|||
else:
|
||||
logger.debug(f"backend: sending payment {temp_id}")
|
||||
# actually pay the external invoice
|
||||
WALLET = get_wallet_class()
|
||||
payment: PaymentResponse = await WALLET.pay_invoice(
|
||||
payment_request, fee_reserve_msat
|
||||
)
|
||||
|
@ -381,7 +395,88 @@ async def check_transaction_status(
|
|||
|
||||
# WARN: this same value must be used for balance check and passed to WALLET.pay_invoice(), it may cause a vulnerability if the values differ
|
||||
def fee_reserve(amount_msat: int) -> int:
|
||||
return max(int(RESERVE_FEE_MIN), int(amount_msat * RESERVE_FEE_PERCENT / 100.0))
|
||||
reserve_min = settings.lnbits_reserve_fee_min
|
||||
reserve_percent = settings.lnbits_reserve_fee_percent
|
||||
return max(int(reserve_min), int(amount_msat * reserve_percent / 100.0))
|
||||
|
||||
|
||||
async def update_wallet_balance(wallet_id: str, amount: int):
|
||||
internal_id = f"internal_{urlsafe_short_hash()}"
|
||||
payment = await create_payment(
|
||||
wallet_id=wallet_id,
|
||||
checking_id=internal_id,
|
||||
payment_request="admin_internal",
|
||||
payment_hash="admin_internal",
|
||||
amount=amount * 1000,
|
||||
memo="Admin top up",
|
||||
pending=False,
|
||||
)
|
||||
# manually send this for now
|
||||
from lnbits.tasks import internal_invoice_queue
|
||||
|
||||
await internal_invoice_queue.put(internal_id)
|
||||
return payment
|
||||
|
||||
|
||||
async def check_admin_settings():
|
||||
if settings.lnbits_admin_ui:
|
||||
settings_db = await get_super_settings()
|
||||
if not settings_db:
|
||||
# create new settings if table is empty
|
||||
logger.warning("Settings DB empty. Inserting default settings.")
|
||||
settings_db = await init_admin_settings(settings.super_user)
|
||||
logger.warning("Initialized settings from enviroment variables.")
|
||||
|
||||
if settings.super_user and settings.super_user != settings_db.super_user:
|
||||
# .env super_user overwrites DB super_user
|
||||
settings_db = await update_super_user(settings.super_user)
|
||||
|
||||
update_cached_settings(settings_db.dict())
|
||||
|
||||
# printing settings for debugging
|
||||
logger.debug(f"Admin settings:")
|
||||
for key, value in settings.dict(exclude_none=True).items():
|
||||
logger.debug(f"{key}: {value}")
|
||||
|
||||
http = "https" if settings.lnbits_force_https else "http"
|
||||
admin_url = (
|
||||
f"{http}://{settings.host}:{settings.port}/wallet?usr={settings.super_user}"
|
||||
)
|
||||
logger.success(f"✔️ Access super user account at: {admin_url}")
|
||||
|
||||
# callback for saas
|
||||
if (
|
||||
settings.lnbits_saas_callback
|
||||
and settings.lnbits_saas_secret
|
||||
and settings.lnbits_saas_instance_id
|
||||
):
|
||||
send_admin_user_to_saas()
|
||||
|
||||
|
||||
def update_cached_settings(sets_dict: dict):
|
||||
for key, value in sets_dict.items():
|
||||
if not key in readonly_variables:
|
||||
try:
|
||||
setattr(settings, key, value)
|
||||
except:
|
||||
logger.error(f"error overriding setting: {key}, value: {value}")
|
||||
if "super_user" in sets_dict:
|
||||
setattr(settings, "super_user", sets_dict["super_user"])
|
||||
|
||||
|
||||
async def init_admin_settings(super_user: str = None):
|
||||
account = None
|
||||
if super_user:
|
||||
account = await get_account(super_user)
|
||||
if not account:
|
||||
account = await create_account()
|
||||
super_user = account.id
|
||||
if not account.wallets or len(account.wallets) == 0:
|
||||
await create_wallet(user_id=account.id)
|
||||
|
||||
editable_settings = EditableSetings.from_dict(settings.dict())
|
||||
|
||||
return await create_admin_settings(account.id, editable_settings.dict())
|
||||
|
||||
|
||||
class WebsocketConnectionManager:
|
||||
|
|
|
@ -259,25 +259,30 @@ new Vue({
|
|||
this.parse.camera.show = false
|
||||
},
|
||||
updateBalance: function (credit) {
|
||||
if (LNBITS_DENOMINATION != 'sats') {
|
||||
credit = credit * 100
|
||||
}
|
||||
LNbits.api
|
||||
.request('PUT', '/api/v1/wallet/balance/' + credit, this.g.wallet.inkey)
|
||||
.catch(err => {
|
||||
LNbits.utils.notifyApiError(err)
|
||||
})
|
||||
.then(response => {
|
||||
let data = response.data
|
||||
if (data.status === 'ERROR') {
|
||||
this.$q.notify({
|
||||
timeout: 5000,
|
||||
type: 'warning',
|
||||
message: `Failed to update.`
|
||||
})
|
||||
return
|
||||
.request(
|
||||
'PUT',
|
||||
'/admin/api/v1/topup/?usr=' + this.g.user.id,
|
||||
this.g.user.wallets[0].adminkey,
|
||||
{
|
||||
amount: credit,
|
||||
id: this.g.user.wallets[0].id
|
||||
}
|
||||
this.balance = this.balance + data.balance
|
||||
)
|
||||
.then(response => {
|
||||
this.$q.notify({
|
||||
type: 'positive',
|
||||
message:
|
||||
'Success! Added ' +
|
||||
credit +
|
||||
' sats to ' +
|
||||
this.g.user.wallets[0].id,
|
||||
icon: null
|
||||
})
|
||||
this.balance += parseInt(credit)
|
||||
})
|
||||
.catch(function (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
})
|
||||
},
|
||||
closeReceiveDialog: function () {
|
||||
|
|
95
lnbits/core/templates/admin/_tab_funding.html
Normal file
95
lnbits/core/templates/admin/_tab_funding.html
Normal file
|
@ -0,0 +1,95 @@
|
|||
<q-tab-panel name="funding">
|
||||
<q-card-section class="q-pa-none">
|
||||
<h6 class="q-my-none">Wallets Management</h6>
|
||||
<br />
|
||||
<div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<p>Funding Source Info</p>
|
||||
<ul>
|
||||
{%raw%}
|
||||
<li>Funding Source: {{settings.lnbits_backend_wallet_class}}</li>
|
||||
<li>Balance: {{balance / 1000}} sats</li>
|
||||
{%endraw%}
|
||||
</ul>
|
||||
<br />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row q-col-gutter-md">
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<p>Active Funding<small> (Requires server restart)</small></p>
|
||||
<q-select
|
||||
:disable="!isSuperUser"
|
||||
filled
|
||||
v-model="formData.lnbits_backend_wallet_class"
|
||||
hint="Select the active funding wallet"
|
||||
:options="settings.lnbits_allowed_funding_sources"
|
||||
></q-select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="col-12">
|
||||
<p>Fee reserve</p>
|
||||
<div class="row q-col-gutter-md">
|
||||
<div class="col-6">
|
||||
<q-input
|
||||
type="number"
|
||||
filled
|
||||
v-model="formData.lnbits_reserve_fee_min"
|
||||
label="Reserve fee in msats"
|
||||
>
|
||||
</q-input>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<q-input
|
||||
type="number"
|
||||
filled
|
||||
name="lnbits_reserve_fee_percent"
|
||||
v-model="formData.lnbits_reserve_fee_percent"
|
||||
label="Reserve fee in percent"
|
||||
step="0.1"
|
||||
></q-input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="isSuperUser">
|
||||
<p class="q-my-md">
|
||||
Funding Sources<small> (Requires server restart)</small>
|
||||
</p>
|
||||
<q-list
|
||||
v-for="(fund, idx) in settings.lnbits_allowed_funding_sources"
|
||||
:key="idx"
|
||||
>
|
||||
<q-expansion-item
|
||||
expand-separator
|
||||
icon="payments"
|
||||
:label="fund"
|
||||
v-if="funding_sources.get(fund)"
|
||||
>
|
||||
<q-card>
|
||||
<q-card-section
|
||||
v-for="([key, prop], i) in Object.entries(funding_sources.get(fund))"
|
||||
:key="i"
|
||||
>
|
||||
<q-input
|
||||
dense
|
||||
filled
|
||||
type="text"
|
||||
v-model="formData[key]"
|
||||
:label="prop.label"
|
||||
class="q-pr-md"
|
||||
:hint="prop.hint"
|
||||
></q-input>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</q-expansion-item>
|
||||
</q-list>
|
||||
</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
</q-tab-panel>
|
74
lnbits/core/templates/admin/_tab_server.html
Normal file
74
lnbits/core/templates/admin/_tab_server.html
Normal file
|
@ -0,0 +1,74 @@
|
|||
<q-tab-panel name="server">
|
||||
<q-card-section class="q-pa-none">
|
||||
<h6 class="q-my-none">Server Management</h6>
|
||||
<br />
|
||||
<div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<p>Server Info</p>
|
||||
<ul>
|
||||
{%raw%}
|
||||
<li v-if="settings.lnbits_data_folder">
|
||||
SQlite: {{settings.lnbits_data_folder}}
|
||||
</li>
|
||||
<li v-if="settings.lnbits_database_url">
|
||||
Postgres: {{settings.lnbits_database_url}}
|
||||
</li>
|
||||
{%endraw%}
|
||||
</ul>
|
||||
<br />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row q-col-gutter-md">
|
||||
<div class="col-12 col-md-6">
|
||||
<p>Service Fee</p>
|
||||
<q-input
|
||||
filled
|
||||
type="number"
|
||||
v-model.number="formData.lnbits_service_fee"
|
||||
label="Service fee (%)"
|
||||
step="0.1"
|
||||
hint="Fee charged per tx (%)"
|
||||
></q-input>
|
||||
<br />
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<p>Miscelaneous</p>
|
||||
<q-item tag="label" v-ripple>
|
||||
<q-item-section>
|
||||
<q-item-label>Force HTTPS</q-item-label>
|
||||
<q-item-label caption>Prefer secure URLs</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section avatar>
|
||||
<q-toggle
|
||||
size="md"
|
||||
v-model="formData.lnbits_force_https"
|
||||
checked-icon="check"
|
||||
color="green"
|
||||
unchecked-icon="clear"
|
||||
/>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item tag="label" v-ripple>
|
||||
<q-item-section>
|
||||
<q-item-label>Hide API</q-item-label>
|
||||
<q-item-label caption
|
||||
>Hides wallet api, extensions can choose to honor</q-item-label
|
||||
>
|
||||
</q-item-section>
|
||||
<q-item-section avatar>
|
||||
<q-toggle
|
||||
size="md"
|
||||
v-model="formData.lnbits_hide_api"
|
||||
checked-icon="check"
|
||||
color="green"
|
||||
unchecked-icon="clear"
|
||||
/>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<br />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
</q-tab-panel>
|
117
lnbits/core/templates/admin/_tab_theme.html
Normal file
117
lnbits/core/templates/admin/_tab_theme.html
Normal file
|
@ -0,0 +1,117 @@
|
|||
<q-tab-panel name="theme">
|
||||
<q-card-section class="q-pa-none">
|
||||
<h6 class="q-my-none">UI Management</h6>
|
||||
<br />
|
||||
<div>
|
||||
<div class="row q-col-gutter-md">
|
||||
<div class="col-12 col-md-6">
|
||||
<p>Site Title</p>
|
||||
<q-input
|
||||
filled
|
||||
type="text"
|
||||
v-model="formData.lnbits_site_title"
|
||||
label="Site title"
|
||||
></q-input>
|
||||
<br />
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<p>Site Tagline</p>
|
||||
<q-input
|
||||
filled
|
||||
type="text"
|
||||
v-model="formData.lnbits_site_tagline"
|
||||
label="Site tagline"
|
||||
></q-input>
|
||||
<br />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p>Site Description</p>
|
||||
<q-input
|
||||
v-model="formData.lnbits_site_description"
|
||||
filled
|
||||
type="textarea"
|
||||
hint="Use plain text or raw HTML"
|
||||
/>
|
||||
</div>
|
||||
<br />
|
||||
<div class="row q-col-gutter-md">
|
||||
<div class="col-12 col-md-6">
|
||||
<p>Default Wallet Name</p>
|
||||
<q-input
|
||||
filled
|
||||
type="text"
|
||||
v-model="formData.lnbits_default_wallet_name"
|
||||
label="LNbits wallet"
|
||||
></q-input>
|
||||
<br />
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<p>Denomination</p>
|
||||
<q-input
|
||||
filled
|
||||
type="text"
|
||||
v-model="formData.lnbits_denomination"
|
||||
label="sats"
|
||||
hint="The name for the FakeWallet token"
|
||||
></q-input>
|
||||
<br />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row q-col-gutter-md">
|
||||
<div class="col-12 col-md-6">
|
||||
<p>Themes</p>
|
||||
<q-select
|
||||
filled
|
||||
v-model="formData.lnbits_theme_options"
|
||||
multiple
|
||||
hint="Choose themes available for users"
|
||||
:options="lnbits_theme_options"
|
||||
label="Themes"
|
||||
></q-select>
|
||||
<br />
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<p>Custom Logo</p>
|
||||
<q-input
|
||||
filled
|
||||
type="text"
|
||||
v-model="formData.lnbits_custom_logo"
|
||||
label="https://example.com/image.png"
|
||||
hint="URL to logo image"
|
||||
></q-input>
|
||||
<br />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row q-col-gutter-md">
|
||||
<div class="col-12 col-md-6">
|
||||
<p>Ad Space Title</p>
|
||||
<q-input
|
||||
filled
|
||||
type="text"
|
||||
v-model="formData.lnbits_ad_space_title"
|
||||
label="Supported by"
|
||||
></q-input>
|
||||
<br />
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<p>Advertisement Slots</p>
|
||||
<q-input
|
||||
class="q-mb-md"
|
||||
filled
|
||||
v-model="formData.lnbits_ad_space"
|
||||
type="text"
|
||||
label="url;img_light_url;img_dark_url, url..."
|
||||
hint="Ad url and image filepaths in CSV format, extensions can choose to honor"
|
||||
>
|
||||
</q-input>
|
||||
<q-toggle
|
||||
v-model="formData.lnbits_ad_space_enabled"
|
||||
:label="formData.lnbits_ad_space_enabled ? 'Ads enabled' : 'Ads disabled'"
|
||||
/>
|
||||
<br />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
</q-tab-panel>
|
88
lnbits/core/templates/admin/_tab_users.html
Normal file
88
lnbits/core/templates/admin/_tab_users.html
Normal file
|
@ -0,0 +1,88 @@
|
|||
<q-tab-panel name="users">
|
||||
<q-card-section class="q-pa-none">
|
||||
<h6 class="q-my-none">User Management</h6>
|
||||
<br />
|
||||
<div>
|
||||
<p>Admin Users</p>
|
||||
<q-input
|
||||
filled
|
||||
v-model="formAddAdmin"
|
||||
@keydown.enter="addAdminUser"
|
||||
type="text"
|
||||
label="User ID"
|
||||
hint="Users with admin privileges"
|
||||
>
|
||||
<q-btn @click="addAdminUser" dense flat icon="add"></q-btn>
|
||||
</q-input>
|
||||
<div>
|
||||
{%raw%}
|
||||
<q-chip
|
||||
v-for="user in formData.lnbits_admin_users"
|
||||
:key="user"
|
||||
removable
|
||||
@remove="removeAdminUser(user)"
|
||||
color="primary"
|
||||
text-color="white"
|
||||
>
|
||||
{{ user }}
|
||||
</q-chip>
|
||||
{%endraw%}
|
||||
</div>
|
||||
<br />
|
||||
</div>
|
||||
<div>
|
||||
<p>Allowed Users</p>
|
||||
<q-input
|
||||
filled
|
||||
v-model="formAddUser"
|
||||
@keydown.enter="addAllowedUser"
|
||||
type="text"
|
||||
label="User ID"
|
||||
hint="Only these users can use LNbits"
|
||||
>
|
||||
<q-btn @click="addAllowedUser" dense flat icon="add"></q-btn>
|
||||
</q-input>
|
||||
<div>
|
||||
{% raw %}
|
||||
<q-chip
|
||||
v-for="user in formData.lnbits_allowed_users"
|
||||
:key="user"
|
||||
removable
|
||||
@remove="removeAllowedUser(user)"
|
||||
color="primary"
|
||||
text-color="white"
|
||||
>
|
||||
{{ user }}
|
||||
</q-chip>
|
||||
{% endraw %}
|
||||
</div>
|
||||
<br />
|
||||
</div>
|
||||
<div class="row q-col-gutter-md">
|
||||
<div class="col-12 col-md-6">
|
||||
<p>Admin Extensions</p>
|
||||
<q-select
|
||||
filled
|
||||
v-model="formData.lnbits_admin_extensions"
|
||||
multiple
|
||||
hint="Extensions only user with admin privileges can use"
|
||||
label="Admin extensions"
|
||||
:options="g.extensions.map(e => e.name)"
|
||||
></q-select>
|
||||
<br />
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<p>Disabled Extensions</p>
|
||||
<q-select
|
||||
filled
|
||||
v-model="formData.lnbits_disabled_extensions"
|
||||
:options="g.extensions.map(e => e.name)"
|
||||
multiple
|
||||
hint="Disable extensions *amilk disabled by default as resource heavy"
|
||||
label="Disable extensions"
|
||||
></q-select>
|
||||
<br />
|
||||
</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
</q-tab-panel>
|
529
lnbits/core/templates/admin/index.html
Normal file
529
lnbits/core/templates/admin/index.html
Normal file
|
@ -0,0 +1,529 @@
|
|||
{% 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">
|
||||
<q-btn
|
||||
label="Save"
|
||||
color="primary"
|
||||
@click="updateSettings"
|
||||
:disabled="!checkChanges"
|
||||
>
|
||||
<q-tooltip v-if="checkChanges"> Save your changes </q-tooltip>
|
||||
<q-badge
|
||||
v-if="checkChanges"
|
||||
color="red"
|
||||
rounded
|
||||
floating
|
||||
style="padding: 6px; border-radius: 6px"
|
||||
/>
|
||||
</q-btn>
|
||||
<q-btn
|
||||
v-if="isSuperUser"
|
||||
label="Restart server"
|
||||
color="primary"
|
||||
@click="restartServer"
|
||||
>
|
||||
<q-tooltip v-if="needsRestart">
|
||||
Restart the server for changes to take effect
|
||||
</q-tooltip>
|
||||
<q-badge
|
||||
v-if="needsRestart"
|
||||
color="red"
|
||||
rounded
|
||||
floating
|
||||
style="padding: 6px; border-radius: 6px"
|
||||
/>
|
||||
</q-btn>
|
||||
<q-btn
|
||||
v-if="isSuperUser"
|
||||
label="Topup"
|
||||
color="primary"
|
||||
@click="topUpDialog.show = true"
|
||||
>
|
||||
<q-tooltip> Add funds to a wallet. </q-tooltip>
|
||||
</q-btn>
|
||||
<!-- <q-btn
|
||||
label="Download Database Backup"
|
||||
flat
|
||||
@click="downloadBackup"
|
||||
></q-btn> -->
|
||||
<q-btn
|
||||
flat
|
||||
v-if="isSuperUser"
|
||||
label="Reset to defaults"
|
||||
color="primary"
|
||||
@click="deleteSettings"
|
||||
class="float-right"
|
||||
>
|
||||
<q-tooltip> Delete all settings and reset to defaults. </q-tooltip>
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row q-col-gutter-md justify-center">
|
||||
<div class="col q-gutter-y-md">
|
||||
<q-card>
|
||||
<div class="q-pa-md">
|
||||
<div class="q-gutter-y-md">
|
||||
<q-tabs v-model="tab" active-color="primary" align="justify">
|
||||
<q-tab
|
||||
name="funding"
|
||||
label="Funding"
|
||||
@update="val => tab = val.name"
|
||||
></q-tab>
|
||||
<q-tab
|
||||
name="users"
|
||||
label="Users"
|
||||
@update="val => tab = val.name"
|
||||
></q-tab>
|
||||
<q-tab
|
||||
name="server"
|
||||
label="Server"
|
||||
@update="val => tab = val.name"
|
||||
></q-tab>
|
||||
<q-tab
|
||||
name="theme"
|
||||
label="Theme"
|
||||
@update="val => tab = val.name"
|
||||
></q-tab>
|
||||
</q-tabs>
|
||||
</div>
|
||||
</div>
|
||||
<q-form name="settings_form" id="settings_form">
|
||||
<q-tab-panels v-model="tab" animated>
|
||||
{% include "admin/_tab_funding.html" %} {% include
|
||||
"admin/_tab_users.html" %} {% include "admin/_tab_server.html" %} {%
|
||||
include "admin/_tab_theme.html" %}
|
||||
</q-tab-panels>
|
||||
</q-form>
|
||||
</q-card>
|
||||
</div>
|
||||
</div>
|
||||
<q-dialog v-if="isSuperUser" v-model="topUpDialog.show" position="top">
|
||||
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
|
||||
<q-form class="q-gutter-md">
|
||||
<p>TopUp a wallet</p>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<q-input
|
||||
dense
|
||||
type="text"
|
||||
filled
|
||||
v-model="wallet.id"
|
||||
label="Wallet ID"
|
||||
hint="Use the wallet ID to topup any wallet"
|
||||
></q-input>
|
||||
<br />
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<q-input
|
||||
dense
|
||||
type="number"
|
||||
filled
|
||||
v-model="wallet.amount"
|
||||
label="Topup amount"
|
||||
></q-input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row q-mt-lg">
|
||||
<q-btn label="Topup" color="primary" @click="topupWallet"></q-btn>
|
||||
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn>
|
||||
</div>
|
||||
</q-form>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
|
||||
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
||||
<script>
|
||||
new Vue({
|
||||
el: '#vue',
|
||||
mixins: [windowMixin],
|
||||
data: function () {
|
||||
return {
|
||||
settings: {},
|
||||
lnbits_theme_options: [
|
||||
'classic',
|
||||
'bitcoin',
|
||||
'flamingo',
|
||||
'freedom',
|
||||
'mint',
|
||||
'autumn',
|
||||
'monochrome',
|
||||
'salvador'
|
||||
],
|
||||
formData: {},
|
||||
formAddAdmin: '',
|
||||
formAddUser: '',
|
||||
isSuperUser: false,
|
||||
wallet: {},
|
||||
cancel: {},
|
||||
topUpDialog: {
|
||||
show: false
|
||||
},
|
||||
tab: 'funding',
|
||||
needsRestart: false,
|
||||
funding_sources: new Map([
|
||||
['VoidWallet', null],
|
||||
[
|
||||
'FakeWallet',
|
||||
{
|
||||
fake_wallet_secret: {
|
||||
value: null,
|
||||
label: 'Secret'
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
'CLightningWallet',
|
||||
{
|
||||
corelightning_rpc: {
|
||||
value: null,
|
||||
label: 'Endpoint'
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
'LndRestWallet',
|
||||
{
|
||||
lnd_rest_endpoint: {
|
||||
value: null,
|
||||
label: 'Endpoint'
|
||||
},
|
||||
lnd_rest_cert: {
|
||||
value: null,
|
||||
label: 'Certificate'
|
||||
},
|
||||
lnd_rest_macaroon: {
|
||||
value: null,
|
||||
label: 'Macaroon'
|
||||
},
|
||||
lnd_rest_macaroon_encrypted: {
|
||||
value: null,
|
||||
label: 'Encrypted Macaroon'
|
||||
},
|
||||
lnd_cert: {
|
||||
value: null,
|
||||
label: 'Certificate'
|
||||
},
|
||||
lnd_admin_macaroon: {
|
||||
value: null,
|
||||
label: 'Admin Macaroon'
|
||||
},
|
||||
lnd_invoice_macaroon: {
|
||||
value: null,
|
||||
label: 'Invoice Macaroon'
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
'LndWallet',
|
||||
{
|
||||
lnd_grpc_endpoint: {
|
||||
value: null,
|
||||
label: 'Endpoint'
|
||||
},
|
||||
lnd_grpc_cert: {
|
||||
value: null,
|
||||
label: 'Certificate'
|
||||
},
|
||||
lnd_grpc_port: {
|
||||
value: null,
|
||||
label: 'Port'
|
||||
},
|
||||
lnd_grpc_admin_macaroon: {
|
||||
value: null,
|
||||
label: 'Admin Macaroon'
|
||||
},
|
||||
lnd_grpc_invoice_macaroon: {
|
||||
value: null,
|
||||
label: 'Invoice Macaroon'
|
||||
},
|
||||
lnd_grpc_macaroon_encrypted: {
|
||||
value: null,
|
||||
label: 'Encrypted Macaroon'
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
'LntxbotWallet',
|
||||
{
|
||||
lntxbot_api_endpoint: {
|
||||
value: null,
|
||||
label: 'Endpoint'
|
||||
},
|
||||
lntxbot_key: {
|
||||
value: null,
|
||||
label: 'Key'
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
'LNPayWallet',
|
||||
{
|
||||
lnpay_api_endpoint: {
|
||||
value: null,
|
||||
label: 'Endpoint'
|
||||
},
|
||||
lnpay_api_key: {
|
||||
value: null,
|
||||
label: 'API Key'
|
||||
},
|
||||
lnpay_wallet_key: {
|
||||
value: null,
|
||||
label: 'Wallet Key'
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
'EclairWallet',
|
||||
{
|
||||
eclair_url: {
|
||||
value: null,
|
||||
label: 'Endpoint'
|
||||
},
|
||||
eclair_pass: {
|
||||
value: null,
|
||||
label: 'Password'
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
'LNbitsWallet',
|
||||
{
|
||||
lnbits_endpoint: {
|
||||
value: null,
|
||||
label: 'Endpoint'
|
||||
},
|
||||
lnbits_key: {
|
||||
value: null,
|
||||
label: 'Admin Key'
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
'OpenNodeWallet',
|
||||
{
|
||||
opennode_api_endpoint: {
|
||||
value: null,
|
||||
label: 'Endpoint'
|
||||
},
|
||||
opennode_key: {
|
||||
value: null,
|
||||
label: 'Key'
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
'ClicheWallet',
|
||||
{
|
||||
cliche_endpoint: {
|
||||
value: null,
|
||||
label: 'Endpoint'
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
'SparkWallet',
|
||||
{
|
||||
spark_url: {
|
||||
value: null,
|
||||
label: 'Endpoint'
|
||||
},
|
||||
spark_token: {
|
||||
value: null,
|
||||
label: 'Token'
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
'LnTipsWallet',
|
||||
{
|
||||
lntips_api_endpoint: {
|
||||
value: null,
|
||||
label: 'Endpoint'
|
||||
},
|
||||
lntips_api_key: {
|
||||
value: null,
|
||||
label: 'API Key'
|
||||
}
|
||||
}
|
||||
]
|
||||
])
|
||||
}
|
||||
},
|
||||
created: function () {
|
||||
this.getSettings()
|
||||
this.balance = +'{{ balance|safe }}'
|
||||
},
|
||||
computed: {
|
||||
checkChanges() {
|
||||
return !_.isEqual(this.settings, this.formData)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addAdminUser() {
|
||||
let addUser = this.formAddAdmin
|
||||
let admin_users = this.formData.lnbits_admin_users
|
||||
if (addUser && addUser.length && !admin_users.includes(addUser)) {
|
||||
//admin_users = [...admin_users, addUser]
|
||||
this.formData.lnbits_admin_users = [...admin_users, addUser]
|
||||
this.formAddAdmin = ''
|
||||
//console.log(this.checkChanges)
|
||||
}
|
||||
},
|
||||
removeAdminUser(user) {
|
||||
let admin_users = this.formData.lnbits_admin_users
|
||||
this.formData.lnbits_admin_users = admin_users.filter(u => u !== user)
|
||||
},
|
||||
addAllowedUser() {
|
||||
let addUser = this.formAddUser
|
||||
let allowed_users = this.formData.lnbits_allowed_users
|
||||
if (addUser && addUser.length && !allowed_users.includes(addUser)) {
|
||||
this.formData.lnbits_allowed_users = [...allowed_users, addUser]
|
||||
this.formAddUser = ''
|
||||
}
|
||||
},
|
||||
removeAllowedUser(user) {
|
||||
let allowed_users = this.formData.lnbits_allowed_users
|
||||
this.formData.lnbits_allowed_users = allowed_users.filter(
|
||||
u => u !== user
|
||||
)
|
||||
},
|
||||
restartServer() {
|
||||
LNbits.api
|
||||
.request('GET', '/admin/api/v1/restart/?usr=' + this.g.user.id)
|
||||
.then(response => {
|
||||
this.$q.notify({
|
||||
type: 'positive',
|
||||
message: 'Success! Restarted Server',
|
||||
icon: null
|
||||
})
|
||||
this.needsRestart = false
|
||||
})
|
||||
.catch(function (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
})
|
||||
},
|
||||
topupWallet() {
|
||||
LNbits.api
|
||||
.request(
|
||||
'PUT',
|
||||
'/admin/api/v1/topup/?usr=' + this.g.user.id,
|
||||
this.g.user.wallets[0].adminkey,
|
||||
this.wallet
|
||||
)
|
||||
.then(response => {
|
||||
this.$q.notify({
|
||||
type: 'positive',
|
||||
message:
|
||||
'Success! Added ' +
|
||||
this.wallet.amount +
|
||||
' to ' +
|
||||
this.wallet.id,
|
||||
icon: null
|
||||
})
|
||||
this.wallet = {}
|
||||
})
|
||||
.catch(function (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
})
|
||||
},
|
||||
updateFundingData() {
|
||||
this.settings.lnbits_allowed_funding_sources.map(f => {
|
||||
let opts = this.funding_sources.get(f)
|
||||
if (!opts) return
|
||||
|
||||
Object.keys(opts).forEach(e => {
|
||||
opts[e].value = this.settings[e]
|
||||
})
|
||||
})
|
||||
},
|
||||
getSettings() {
|
||||
LNbits.api
|
||||
.request(
|
||||
'GET',
|
||||
'/admin/api/v1/settings/?usr=' + this.g.user.id,
|
||||
this.g.user.wallets[0].adminkey
|
||||
)
|
||||
.then(response => {
|
||||
this.isSuperUser = response.data.super_user || false
|
||||
this.settings = response.data
|
||||
this.formData = _.clone(this.settings)
|
||||
this.updateFundingData()
|
||||
})
|
||||
.catch(function (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
})
|
||||
},
|
||||
updateSettings() {
|
||||
let data = _.omit(this.formData, [
|
||||
'super_user',
|
||||
'lnbits_allowed_funding_sources'
|
||||
])
|
||||
LNbits.api
|
||||
.request(
|
||||
'PUT',
|
||||
'/admin/api/v1/settings/?usr=' + this.g.user.id,
|
||||
this.g.user.wallets[0].adminkey,
|
||||
data
|
||||
)
|
||||
.then(response => {
|
||||
this.needsRestart =
|
||||
this.settings.lnbits_backend_wallet_class !==
|
||||
this.formData.lnbits_backend_wallet_class
|
||||
this.settings = this.formData
|
||||
this.formData = _.clone(this.settings)
|
||||
this.updateFundingData()
|
||||
this.$q.notify({
|
||||
type: 'positive',
|
||||
message: 'Success! Settings changed!',
|
||||
icon: null
|
||||
})
|
||||
})
|
||||
.catch(function (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
})
|
||||
},
|
||||
deleteSettings() {
|
||||
LNbits.utils
|
||||
.confirmDialog(
|
||||
'Are you sure you want to restore settings to default?'
|
||||
)
|
||||
.onOk(() => {
|
||||
LNbits.api
|
||||
.request(
|
||||
'DELETE',
|
||||
'/admin/api/v1/settings/?usr=' + this.g.user.id
|
||||
)
|
||||
.then(response => {
|
||||
this.$q.notify({
|
||||
type: 'positive',
|
||||
message:
|
||||
'Success! Restored settings to defaults, restart required!',
|
||||
icon: null
|
||||
})
|
||||
this.needsRestart = true
|
||||
})
|
||||
.catch(function (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
downloadBackup() {
|
||||
LNbits.api
|
||||
.request('GET', '/admin/api/v1/backup/?usr=' + this.g.user.id)
|
||||
.then(response => {
|
||||
this.$q.notify({
|
||||
type: 'positive',
|
||||
message:
|
||||
'Success! Database backup request, download starts soon!',
|
||||
icon: null
|
||||
})
|
||||
})
|
||||
.catch(function (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -82,7 +82,7 @@
|
|||
>
|
||||
</div>
|
||||
</div>
|
||||
<p v-else>{{SITE_DESCRIPTION}}</p>
|
||||
<p v-else>{{SITE_DESCRIPTION | safe}}</p>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</div>
|
||||
|
|
File diff suppressed because it is too large
Load Diff
74
lnbits/core/views/admin_api.py
Normal file
74
lnbits/core/views/admin_api.py
Normal file
|
@ -0,0 +1,74 @@
|
|||
from http import HTTPStatus
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import Body, Depends
|
||||
from starlette.exceptions import HTTPException
|
||||
|
||||
from lnbits.core.crud import get_wallet
|
||||
from lnbits.core.models import User
|
||||
from lnbits.core.services import update_cached_settings, update_wallet_balance
|
||||
from lnbits.decorators import check_admin, check_super_user
|
||||
from lnbits.server import server_restart
|
||||
from lnbits.settings import AdminSettings, EditableSetings
|
||||
|
||||
from .. import core_app
|
||||
from ..crud import delete_admin_settings, get_admin_settings, update_admin_settings
|
||||
|
||||
|
||||
@core_app.get(
|
||||
"/admin/api/v1/restart/",
|
||||
status_code=HTTPStatus.OK,
|
||||
dependencies=[Depends(check_super_user)],
|
||||
)
|
||||
async def api_restart_server() -> dict[str, str]:
|
||||
server_restart.set()
|
||||
return {"status": "Success"}
|
||||
|
||||
|
||||
@core_app.get("/admin/api/v1/settings/")
|
||||
async def api_get_settings(
|
||||
user: User = Depends(check_admin), # type: ignore
|
||||
) -> Optional[AdminSettings]:
|
||||
admin_settings = await get_admin_settings(user.super_user)
|
||||
return admin_settings
|
||||
|
||||
|
||||
@core_app.put(
|
||||
"/admin/api/v1/topup/",
|
||||
status_code=HTTPStatus.OK,
|
||||
dependencies=[Depends(check_admin)],
|
||||
)
|
||||
async def api_topup_balance(
|
||||
id: str = Body(...), amount: int = Body(...)
|
||||
) -> dict[str, str]:
|
||||
try:
|
||||
await get_wallet(id)
|
||||
except:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.FORBIDDEN, detail="wallet does not exist."
|
||||
)
|
||||
|
||||
await update_wallet_balance(wallet_id=id, amount=int(amount))
|
||||
|
||||
return {"status": "Success"}
|
||||
|
||||
|
||||
@core_app.put(
|
||||
"/admin/api/v1/settings/",
|
||||
status_code=HTTPStatus.OK,
|
||||
dependencies=[Depends(check_admin)],
|
||||
)
|
||||
async def api_update_settings(data: EditableSetings):
|
||||
await update_admin_settings(data)
|
||||
update_cached_settings(dict(data))
|
||||
return {"status": "Success"}
|
||||
|
||||
|
||||
@core_app.delete(
|
||||
"/admin/api/v1/settings/",
|
||||
status_code=HTTPStatus.OK,
|
||||
dependencies=[Depends(check_admin)],
|
||||
)
|
||||
async def api_delete_settings() -> dict[str, str]:
|
||||
await delete_admin_settings()
|
||||
return {"status": "Success"}
|
|
@ -6,7 +6,7 @@ import time
|
|||
import uuid
|
||||
from http import HTTPStatus
|
||||
from io import BytesIO
|
||||
from typing import Dict, List, Optional, Tuple, Union
|
||||
from typing import Dict, Optional, Tuple, Union
|
||||
from urllib.parse import ParseResult, parse_qs, urlencode, urlparse, urlunparse
|
||||
|
||||
import async_timeout
|
||||
|
@ -26,19 +26,20 @@ from fastapi.params import Body
|
|||
from loguru import logger
|
||||
from pydantic import BaseModel
|
||||
from pydantic.fields import Field
|
||||
from sse_starlette.sse import EventSourceResponse, ServerSentEvent
|
||||
from starlette.responses import HTMLResponse, StreamingResponse
|
||||
from sse_starlette.sse import EventSourceResponse
|
||||
from starlette.responses import StreamingResponse
|
||||
|
||||
from lnbits import bolt11, lnurl
|
||||
from lnbits.core.models import Payment, Wallet
|
||||
from lnbits.decorators import (
|
||||
WalletTypeInfo,
|
||||
check_admin,
|
||||
get_key_type,
|
||||
require_admin_key,
|
||||
require_invoice_key,
|
||||
)
|
||||
from lnbits.helpers import url_for, urlsafe_short_hash
|
||||
from lnbits.settings import LNBITS_ADMIN_USERS, LNBITS_SITE_TITLE, WALLET
|
||||
from lnbits.settings import get_wallet_class, settings
|
||||
from lnbits.utils.exchange_rates import (
|
||||
currencies,
|
||||
fiat_amount_as_satoshis,
|
||||
|
@ -82,35 +83,6 @@ async def api_wallet(wallet: WalletTypeInfo = Depends(get_key_type)):
|
|||
return {"name": wallet.wallet.name, "balance": wallet.wallet.balance_msat}
|
||||
|
||||
|
||||
@core_app.put("/api/v1/wallet/balance/{amount}")
|
||||
async def api_update_balance(
|
||||
amount: int, wallet: WalletTypeInfo = Depends(get_key_type)
|
||||
):
|
||||
if wallet.wallet.user not in LNBITS_ADMIN_USERS:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.FORBIDDEN, detail="Not an admin user"
|
||||
)
|
||||
|
||||
payHash = urlsafe_short_hash()
|
||||
await create_payment(
|
||||
wallet_id=wallet.wallet.id,
|
||||
checking_id=payHash,
|
||||
payment_request="selfPay",
|
||||
payment_hash=payHash,
|
||||
amount=amount * 1000,
|
||||
memo="selfPay",
|
||||
fee=0,
|
||||
)
|
||||
await update_payment_status(checking_id=payHash, pending=False)
|
||||
updatedWallet = await get_wallet(wallet.wallet.id)
|
||||
|
||||
return {
|
||||
"id": wallet.wallet.id,
|
||||
"name": wallet.wallet.name,
|
||||
"balance": amount,
|
||||
}
|
||||
|
||||
|
||||
@core_app.put("/api/v1/wallet/{new_name}")
|
||||
async def api_update_wallet(
|
||||
new_name: str, wallet: WalletTypeInfo = Depends(require_admin_key)
|
||||
|
@ -186,7 +158,7 @@ async def api_payments_create_invoice(data: CreateInvoiceData, wallet: Wallet):
|
|||
else:
|
||||
description_hash = b""
|
||||
unhashed_description = b""
|
||||
memo = data.memo or LNBITS_SITE_TITLE
|
||||
memo = data.memo or settings.lnbits_site_title
|
||||
|
||||
if data.unit == "sat":
|
||||
amount = int(data.amount)
|
||||
|
@ -416,7 +388,7 @@ async def subscribe_wallet_invoices(request: Request, wallet: Wallet):
|
|||
|
||||
yield dict(data=jdata, event=typ)
|
||||
except asyncio.CancelledError as e:
|
||||
logger.debug(f"CancelledError on listener {uid}: {e}")
|
||||
logger.debug(f"removing listener for wallet {uid}")
|
||||
api_invoice_listeners.pop(uid)
|
||||
task.cancel()
|
||||
return
|
||||
|
@ -686,13 +658,9 @@ async def img(request: Request, data):
|
|||
)
|
||||
|
||||
|
||||
@core_app.get("/api/v1/audit")
|
||||
async def api_auditor(wallet: WalletTypeInfo = Depends(get_key_type)):
|
||||
if wallet.wallet.user not in LNBITS_ADMIN_USERS:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.FORBIDDEN, detail="Not an admin user"
|
||||
)
|
||||
|
||||
@core_app.get("/api/v1/audit/", dependencies=[Depends(check_admin)])
|
||||
async def api_auditor():
|
||||
WALLET = get_wallet_class()
|
||||
total_balance = await get_total_balance()
|
||||
error_message, node_balance = await WALLET.status()
|
||||
|
||||
|
|
|
@ -13,15 +13,9 @@ from starlette.responses import HTMLResponse, JSONResponse
|
|||
|
||||
from lnbits.core import db
|
||||
from lnbits.core.models import User
|
||||
from lnbits.decorators import check_user_exists
|
||||
from lnbits.decorators import check_admin, check_user_exists
|
||||
from lnbits.helpers import template_renderer, url_for
|
||||
from lnbits.settings import (
|
||||
LNBITS_ADMIN_USERS,
|
||||
LNBITS_ALLOWED_USERS,
|
||||
LNBITS_CUSTOM_LOGO,
|
||||
LNBITS_SITE_TITLE,
|
||||
SERVICE_FEE,
|
||||
)
|
||||
from lnbits.settings import get_wallet_class, settings
|
||||
|
||||
from ...helpers import get_valid_extensions
|
||||
from ..crud import (
|
||||
|
@ -117,7 +111,6 @@ async def wallet(
|
|||
user_id = usr.hex if usr else None
|
||||
wallet_id = wal.hex if wal else None
|
||||
wallet_name = nme
|
||||
service_fee = int(SERVICE_FEE) if int(SERVICE_FEE) == SERVICE_FEE else SERVICE_FEE
|
||||
|
||||
if not user_id:
|
||||
user = await get_user((await create_account()).id)
|
||||
|
@ -128,11 +121,14 @@ async def wallet(
|
|||
return template_renderer().TemplateResponse(
|
||||
"error.html", {"request": request, "err": "User does not exist."}
|
||||
)
|
||||
if LNBITS_ALLOWED_USERS and user_id not in LNBITS_ALLOWED_USERS:
|
||||
if (
|
||||
len(settings.lnbits_allowed_users) > 0
|
||||
and user_id not in settings.lnbits_allowed_users
|
||||
):
|
||||
return template_renderer().TemplateResponse(
|
||||
"error.html", {"request": request, "err": "User not authorized."}
|
||||
)
|
||||
if LNBITS_ADMIN_USERS and user_id in LNBITS_ADMIN_USERS:
|
||||
if user_id == settings.super_user or user_id in settings.lnbits_admin_users:
|
||||
user.admin = True
|
||||
if not wallet_id:
|
||||
if user.wallets and not wallet_name: # type: ignore
|
||||
|
@ -163,7 +159,7 @@ async def wallet(
|
|||
"request": request,
|
||||
"user": user.dict(), # type: ignore
|
||||
"wallet": userwallet.dict(),
|
||||
"service_fee": service_fee,
|
||||
"service_fee": settings.lnbits_service_fee,
|
||||
"web_manifest": f"/manifest/{user.id}.webmanifest", # type: ignore
|
||||
},
|
||||
)
|
||||
|
@ -185,7 +181,7 @@ async def lnurl_full_withdraw(request: Request):
|
|||
"k1": "0",
|
||||
"minWithdrawable": 1000 if wallet.withdrawable_balance else 0,
|
||||
"maxWithdrawable": wallet.withdrawable_balance,
|
||||
"defaultDescription": f"{LNBITS_SITE_TITLE} balance withdraw from {wallet.id[0:5]}",
|
||||
"defaultDescription": f"{settings.lnbits_site_title} balance withdraw from {wallet.id[0:5]}",
|
||||
"balanceCheck": url_for("/withdraw", external=True, usr=user.id, wal=wallet.id),
|
||||
}
|
||||
|
||||
|
@ -284,12 +280,12 @@ async def manifest(usr: str):
|
|||
raise HTTPException(status_code=HTTPStatus.NOT_FOUND)
|
||||
|
||||
return {
|
||||
"short_name": LNBITS_SITE_TITLE,
|
||||
"name": LNBITS_SITE_TITLE + " Wallet",
|
||||
"short_name": settings.lnbits_site_title,
|
||||
"name": settings.lnbits_site_title + " Wallet",
|
||||
"icons": [
|
||||
{
|
||||
"src": LNBITS_CUSTOM_LOGO
|
||||
if LNBITS_CUSTOM_LOGO
|
||||
"src": settings.lnbits_custom_logo
|
||||
if settings.lnbits_custom_logo
|
||||
else "https://cdn.jsdelivr.net/gh/lnbits/lnbits@0.3.0/docs/logos/lnbits.png",
|
||||
"type": "image/png",
|
||||
"sizes": "900x900",
|
||||
|
@ -311,3 +307,19 @@ async def manifest(usr: str):
|
|||
for wallet in user.wallets
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@core_html_routes.get("/admin", response_class=HTMLResponse)
|
||||
async def index(request: Request, user: User = Depends(check_admin)): # type: ignore
|
||||
WALLET = get_wallet_class()
|
||||
_, balance = await WALLET.status()
|
||||
|
||||
return template_renderer().TemplateResponse(
|
||||
"admin/index.html",
|
||||
{
|
||||
"request": request,
|
||||
"user": user.dict(),
|
||||
"settings": settings.dict(),
|
||||
"balance": balance,
|
||||
},
|
||||
)
|
||||
|
|
16
lnbits/db.py
16
lnbits/db.py
|
@ -11,7 +11,7 @@ from sqlalchemy import create_engine
|
|||
from sqlalchemy_aio.base import AsyncConnection
|
||||
from sqlalchemy_aio.strategy import ASYNCIO_STRATEGY # type: ignore
|
||||
|
||||
from .settings import LNBITS_DATA_FOLDER, LNBITS_DATABASE_URL
|
||||
from lnbits.settings import settings
|
||||
|
||||
POSTGRES = "POSTGRES"
|
||||
COCKROACH = "COCKROACH"
|
||||
|
@ -121,8 +121,8 @@ class Database(Compat):
|
|||
def __init__(self, db_name: str):
|
||||
self.name = db_name
|
||||
|
||||
if LNBITS_DATABASE_URL:
|
||||
database_uri = LNBITS_DATABASE_URL
|
||||
if settings.lnbits_database_url:
|
||||
database_uri = settings.lnbits_database_url
|
||||
|
||||
if database_uri.startswith("cockroachdb://"):
|
||||
self.type = COCKROACH
|
||||
|
@ -162,14 +162,16 @@ class Database(Compat):
|
|||
)
|
||||
)
|
||||
else:
|
||||
if os.path.isdir(LNBITS_DATA_FOLDER):
|
||||
self.path = os.path.join(LNBITS_DATA_FOLDER, f"{self.name}.sqlite3")
|
||||
if os.path.isdir(settings.lnbits_data_folder):
|
||||
self.path = os.path.join(
|
||||
settings.lnbits_data_folder, f"{self.name}.sqlite3"
|
||||
)
|
||||
database_uri = f"sqlite:///{self.path}"
|
||||
self.type = SQLITE
|
||||
else:
|
||||
raise NotADirectoryError(
|
||||
f"LNBITS_DATA_FOLDER named {LNBITS_DATA_FOLDER} was not created"
|
||||
f" - please 'mkdir {LNBITS_DATA_FOLDER}' and try again"
|
||||
f"LNBITS_DATA_FOLDER named {settings.lnbits_data_folder} was not created"
|
||||
f" - please 'mkdir {settings.lnbits_data_folder}' and try again"
|
||||
)
|
||||
logger.trace(f"database {self.type} added for {self.name}")
|
||||
self.schema = self.name
|
||||
|
|
|
@ -14,11 +14,7 @@ from starlette.requests import Request
|
|||
from lnbits.core.crud import get_user, get_wallet_for_key
|
||||
from lnbits.core.models import User, Wallet
|
||||
from lnbits.requestvars import g
|
||||
from lnbits.settings import (
|
||||
LNBITS_ADMIN_EXTENSIONS,
|
||||
LNBITS_ADMIN_USERS,
|
||||
LNBITS_ALLOWED_USERS,
|
||||
)
|
||||
from lnbits.settings import settings
|
||||
|
||||
|
||||
class KeyChecker(SecurityBase):
|
||||
|
@ -150,8 +146,12 @@ async def get_key_type(
|
|||
status_code=HTTPStatus.NOT_FOUND, detail="Wallet does not exist."
|
||||
)
|
||||
if (
|
||||
LNBITS_ADMIN_USERS and wallet.wallet.user not in LNBITS_ADMIN_USERS
|
||||
) and (LNBITS_ADMIN_EXTENSIONS and pathname in LNBITS_ADMIN_EXTENSIONS):
|
||||
wallet.wallet.user != settings.super_user
|
||||
and wallet.wallet.user not in settings.lnbits_admin_users
|
||||
) and (
|
||||
settings.lnbits_admin_extensions
|
||||
and pathname in settings.lnbits_admin_extensions
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.FORBIDDEN,
|
||||
detail="User not authorized for this extension.",
|
||||
|
@ -227,17 +227,45 @@ async def require_invoice_key(
|
|||
|
||||
async def check_user_exists(usr: UUID4) -> User:
|
||||
g().user = await get_user(usr.hex)
|
||||
|
||||
if not g().user:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.NOT_FOUND, detail="User does not exist."
|
||||
status_code=HTTPStatus.NOT_FOUND, detail="User does not exist."
|
||||
)
|
||||
|
||||
if LNBITS_ALLOWED_USERS and g().user.id not in LNBITS_ALLOWED_USERS:
|
||||
if (
|
||||
len(settings.lnbits_allowed_users) > 0
|
||||
and g().user.id not in settings.lnbits_allowed_users
|
||||
and g().user.id != settings.super_user
|
||||
and g().user.id not in settings.lnbits_admin_users
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized."
|
||||
)
|
||||
|
||||
if LNBITS_ADMIN_USERS and g().user.id in LNBITS_ADMIN_USERS:
|
||||
g().user.admin = True
|
||||
|
||||
return g().user
|
||||
|
||||
|
||||
async def check_admin(usr: UUID4) -> User:
|
||||
user = await check_user_exists(usr)
|
||||
if user.id != settings.super_user and not user.id in settings.lnbits_admin_users:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.UNAUTHORIZED,
|
||||
detail="User not authorized. No admin privileges.",
|
||||
)
|
||||
user.admin = True
|
||||
user.super_user = False
|
||||
if user.id == settings.super_user:
|
||||
user.super_user = True
|
||||
|
||||
return user
|
||||
|
||||
|
||||
async def check_super_user(usr: UUID4) -> User:
|
||||
user = await check_admin(usr)
|
||||
if user.id != settings.super_user:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.UNAUTHORIZED,
|
||||
detail="User not authorized. No super user privileges.",
|
||||
)
|
||||
return user
|
||||
|
|
|
@ -12,7 +12,7 @@ from loguru import logger
|
|||
|
||||
from lnbits.core.services import create_invoice, pay_invoice
|
||||
from lnbits.helpers import urlsafe_short_hash
|
||||
from lnbits.settings import BOLTZ_NETWORK, BOLTZ_URL
|
||||
from lnbits.settings import settings
|
||||
|
||||
from .crud import update_swap_status
|
||||
from .mempool import (
|
||||
|
@ -33,9 +33,7 @@ from .models import (
|
|||
)
|
||||
from .utils import check_balance, get_timestamp, req_wrap
|
||||
|
||||
net = NETWORKS[BOLTZ_NETWORK]
|
||||
logger.trace(f"BOLTZ_URL: {BOLTZ_URL}")
|
||||
logger.trace(f"Bitcoin Network: {net['name']}")
|
||||
net = NETWORKS[settings.boltz_network]
|
||||
|
||||
|
||||
async def create_swap(data: CreateSubmarineSwap) -> SubmarineSwap:
|
||||
|
@ -62,7 +60,7 @@ async def create_swap(data: CreateSubmarineSwap) -> SubmarineSwap:
|
|||
|
||||
res = req_wrap(
|
||||
"post",
|
||||
f"{BOLTZ_URL}/createswap",
|
||||
f"{settings.boltz_url}/createswap",
|
||||
json={
|
||||
"type": "submarine",
|
||||
"pairId": "BTC/BTC",
|
||||
|
@ -129,7 +127,7 @@ async def create_reverse_swap(
|
|||
|
||||
res = req_wrap(
|
||||
"post",
|
||||
f"{BOLTZ_URL}/createswap",
|
||||
f"{settings.boltz_url}/createswap",
|
||||
json={
|
||||
"type": "reversesubmarine",
|
||||
"pairId": "BTC/BTC",
|
||||
|
@ -409,7 +407,7 @@ def check_boltz_limits(amount):
|
|||
def get_boltz_pairs():
|
||||
res = req_wrap(
|
||||
"get",
|
||||
f"{BOLTZ_URL}/getpairs",
|
||||
f"{settings.boltz_url}/getpairs",
|
||||
headers={"Content-Type": "application/json"},
|
||||
)
|
||||
return res.json()
|
||||
|
@ -418,7 +416,7 @@ def get_boltz_pairs():
|
|||
def get_boltz_status(boltzid):
|
||||
res = req_wrap(
|
||||
"post",
|
||||
f"{BOLTZ_URL}/swapstatus",
|
||||
f"{settings.boltz_url}/swapstatus",
|
||||
json={"id": boltzid},
|
||||
)
|
||||
return res.json()
|
||||
|
|
|
@ -7,14 +7,11 @@ import websockets
|
|||
from embit.transaction import Transaction
|
||||
from loguru import logger
|
||||
|
||||
from lnbits.settings import BOLTZ_MEMPOOL_SPACE_URL, BOLTZ_MEMPOOL_SPACE_URL_WS
|
||||
from lnbits.settings import settings
|
||||
|
||||
from .utils import req_wrap
|
||||
|
||||
logger.trace(f"BOLTZ_MEMPOOL_SPACE_URL: {BOLTZ_MEMPOOL_SPACE_URL}")
|
||||
logger.trace(f"BOLTZ_MEMPOOL_SPACE_URL_WS: {BOLTZ_MEMPOOL_SPACE_URL_WS}")
|
||||
|
||||
websocket_url = f"{BOLTZ_MEMPOOL_SPACE_URL_WS}/api/v1/ws"
|
||||
websocket_url = f"{settings.boltz_mempool_space_url_ws}/api/v1/ws"
|
||||
|
||||
|
||||
async def wait_for_websocket_message(send, message_string):
|
||||
|
@ -33,7 +30,7 @@ async def wait_for_websocket_message(send, message_string):
|
|||
def get_mempool_tx(address):
|
||||
res = req_wrap(
|
||||
"get",
|
||||
f"{BOLTZ_MEMPOOL_SPACE_URL}/api/address/{address}/txs",
|
||||
f"{settings.boltz_mempool_space_url}/api/address/{address}/txs",
|
||||
headers={"Content-Type": "text/plain"},
|
||||
)
|
||||
txs = res.json()
|
||||
|
@ -70,7 +67,7 @@ def get_fee_estimation() -> int:
|
|||
def get_mempool_fees() -> int:
|
||||
res = req_wrap(
|
||||
"get",
|
||||
f"{BOLTZ_MEMPOOL_SPACE_URL}/api/v1/fees/recommended",
|
||||
f"{settings.boltz_mempool_space_url}/api/v1/fees/recommended",
|
||||
headers={"Content-Type": "text/plain"},
|
||||
)
|
||||
fees = res.json()
|
||||
|
@ -80,7 +77,7 @@ def get_mempool_fees() -> int:
|
|||
def get_mempool_blockheight() -> int:
|
||||
res = req_wrap(
|
||||
"get",
|
||||
f"{BOLTZ_MEMPOOL_SPACE_URL}/api/blocks/tip/height",
|
||||
f"{settings.boltz_mempool_space_url}/api/blocks/tip/height",
|
||||
headers={"Content-Type": "text/plain"},
|
||||
)
|
||||
return int(res.text)
|
||||
|
@ -91,7 +88,7 @@ async def send_onchain_tx(tx: Transaction):
|
|||
logger.debug(f"Boltz - mempool sending onchain tx...")
|
||||
req_wrap(
|
||||
"post",
|
||||
f"{BOLTZ_MEMPOOL_SPACE_URL}/api/tx",
|
||||
f"{settings.boltz_mempool_space_url}/api/tx",
|
||||
headers={"Content-Type": "text/plain"},
|
||||
content=raw,
|
||||
)
|
||||
|
|
|
@ -14,7 +14,7 @@ from starlette.requests import Request
|
|||
|
||||
from lnbits.core.crud import get_user
|
||||
from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
|
||||
from lnbits.settings import BOLTZ_MEMPOOL_SPACE_URL
|
||||
from lnbits.settings import settings
|
||||
|
||||
from . import boltz_ext
|
||||
from .boltz import (
|
||||
|
@ -55,7 +55,7 @@ from .utils import check_balance
|
|||
response_model=str,
|
||||
)
|
||||
async def api_mempool_url():
|
||||
return BOLTZ_MEMPOOL_SPACE_URL
|
||||
return settings.boltz_mempool_space_url
|
||||
|
||||
|
||||
# NORMAL SWAP
|
||||
|
|
|
@ -12,7 +12,7 @@ from lnbits import bolt11
|
|||
from lnbits.core.crud import delete_expired_invoices, get_payments
|
||||
from lnbits.core.services import create_invoice, pay_invoice
|
||||
from lnbits.decorators import WalletTypeInfo
|
||||
from lnbits.settings import LNBITS_SITE_TITLE, WALLET
|
||||
from lnbits.settings import get_wallet_class, settings
|
||||
|
||||
from . import lndhub_ext
|
||||
from .decorators import check_wallet, require_admin_key
|
||||
|
@ -21,7 +21,7 @@ from .utils import decoded_as_lndhub, to_buffer
|
|||
|
||||
@lndhub_ext.get("/ext/getinfo")
|
||||
async def lndhub_getinfo():
|
||||
return {"alias": LNBITS_SITE_TITLE}
|
||||
return {"alias": settings.lnbits_site_title}
|
||||
|
||||
|
||||
class AuthData(BaseModel):
|
||||
|
@ -56,7 +56,7 @@ async def lndhub_addinvoice(
|
|||
_, pr = await create_invoice(
|
||||
wallet_id=wallet.wallet.id,
|
||||
amount=int(data.amt),
|
||||
memo=data.memo or LNBITS_SITE_TITLE,
|
||||
memo=data.memo or settings.lnbits_site_title,
|
||||
extra={"tag": "lndhub"},
|
||||
)
|
||||
except:
|
||||
|
@ -165,6 +165,7 @@ async def lndhub_getuserinvoices(
|
|||
limit: int = Query(20, ge=1, le=20),
|
||||
offset: int = Query(0, ge=0),
|
||||
):
|
||||
WALLET = get_wallet_class()
|
||||
for invoice in await get_payments(
|
||||
wallet_id=wallet.wallet.id,
|
||||
complete=False,
|
||||
|
|
|
@ -1,18 +1,15 @@
|
|||
import json
|
||||
from http import HTTPStatus
|
||||
|
||||
from fastapi import Response
|
||||
from fastapi.param_functions import Depends
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from loguru import logger
|
||||
from starlette.exceptions import HTTPException
|
||||
from starlette.requests import Request
|
||||
from starlette.responses import HTMLResponse
|
||||
|
||||
from lnbits.core.models import User
|
||||
from lnbits.decorators import check_user_exists
|
||||
from lnbits.decorators import check_admin
|
||||
from lnbits.extensions.satspay.helpers import public_charge
|
||||
from lnbits.settings import LNBITS_ADMIN_USERS
|
||||
|
||||
from . import satspay_ext, satspay_renderer
|
||||
from .crud import get_charge, get_theme
|
||||
|
@ -21,17 +18,15 @@ templates = Jinja2Templates(directory="templates")
|
|||
|
||||
|
||||
@satspay_ext.get("/", response_class=HTMLResponse)
|
||||
async def index(request: Request, user: User = Depends(check_user_exists)):
|
||||
admin = False
|
||||
if LNBITS_ADMIN_USERS and user.id in LNBITS_ADMIN_USERS:
|
||||
admin = True
|
||||
async def index(request: Request, user: User = Depends(check_admin)):
|
||||
return satspay_renderer().TemplateResponse(
|
||||
"satspay/index.html", {"request": request, "user": user.dict(), "admin": admin}
|
||||
"satspay/index.html",
|
||||
{"request": request, "user": user.dict(), "admin": user.admin},
|
||||
)
|
||||
|
||||
|
||||
@satspay_ext.get("/{charge_id}", response_class=HTMLResponse)
|
||||
async def display(request: Request, charge_id: str):
|
||||
async def display_charge(request: Request, charge_id: str):
|
||||
charge = await get_charge(charge_id)
|
||||
if not charge:
|
||||
raise HTTPException(
|
||||
|
@ -50,7 +45,7 @@ async def display(request: Request, charge_id: str):
|
|||
|
||||
|
||||
@satspay_ext.get("/css/{css_id}")
|
||||
async def display(css_id: str, response: Response):
|
||||
async def display_css(css_id: str):
|
||||
theme = await get_theme(css_id)
|
||||
if theme:
|
||||
return Response(content=theme.custom_css, media_type="text/css")
|
||||
|
|
|
@ -1,20 +1,19 @@
|
|||
import json
|
||||
from http import HTTPStatus
|
||||
|
||||
import httpx
|
||||
from fastapi import Query
|
||||
from fastapi.params import Depends
|
||||
from loguru import logger
|
||||
from starlette.exceptions import HTTPException
|
||||
|
||||
from lnbits.core.crud import get_wallet
|
||||
from lnbits.decorators import (
|
||||
WalletTypeInfo,
|
||||
check_admin,
|
||||
get_key_type,
|
||||
require_admin_key,
|
||||
require_invoice_key,
|
||||
)
|
||||
from lnbits.extensions.satspay import satspay_ext
|
||||
from lnbits.settings import LNBITS_ADMIN_EXTENSIONS, LNBITS_ADMIN_USERS
|
||||
|
||||
from .crud import (
|
||||
check_address_balance,
|
||||
|
@ -139,18 +138,14 @@ async def api_charge_balance(charge_id):
|
|||
#############################THEMES##########################
|
||||
|
||||
|
||||
@satspay_ext.post("/api/v1/themes")
|
||||
@satspay_ext.post("/api/v1/themes/{css_id}")
|
||||
@satspay_ext.post("/api/v1/themes", dependencies=[Depends(check_admin)])
|
||||
@satspay_ext.post("/api/v1/themes/{css_id}", dependencies=[Depends(check_admin)])
|
||||
async def api_themes_save(
|
||||
data: SatsPayThemes,
|
||||
wallet: WalletTypeInfo = Depends(require_invoice_key),
|
||||
css_id: str = None,
|
||||
css_id: str = Query(...),
|
||||
):
|
||||
if LNBITS_ADMIN_USERS and wallet.wallet.user not in LNBITS_ADMIN_USERS:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.FORBIDDEN,
|
||||
detail="Only server admins can create themes.",
|
||||
)
|
||||
|
||||
if css_id:
|
||||
theme = await save_theme(css_id=css_id, data=data)
|
||||
else:
|
||||
|
|
|
@ -8,7 +8,7 @@ from starlette.responses import HTMLResponse
|
|||
|
||||
from lnbits.core.models import User
|
||||
from lnbits.decorators import check_user_exists
|
||||
from lnbits.settings import LNBITS_CUSTOM_LOGO, LNBITS_SITE_TITLE
|
||||
from lnbits.settings import settings
|
||||
|
||||
from . import tpos_ext, tpos_renderer
|
||||
from .crud import get_tpos
|
||||
|
@ -50,12 +50,12 @@ async def manifest(tpos_id: str):
|
|||
)
|
||||
|
||||
return {
|
||||
"short_name": LNBITS_SITE_TITLE,
|
||||
"name": tpos.name + " - " + LNBITS_SITE_TITLE,
|
||||
"short_name": settings.lnbits_site_title,
|
||||
"name": tpos.name + " - " + settings.lnbits_site_title,
|
||||
"icons": [
|
||||
{
|
||||
"src": LNBITS_CUSTOM_LOGO
|
||||
if LNBITS_CUSTOM_LOGO
|
||||
"src": settings.lnbits_custom_logo
|
||||
if settings.lnbits_custom_logo
|
||||
else "https://cdn.jsdelivr.net/gh/lnbits/lnbits@0.3.0/docs/logos/lnbits.png",
|
||||
"type": "image/png",
|
||||
"sizes": "900x900",
|
||||
|
@ -69,9 +69,9 @@ async def manifest(tpos_id: str):
|
|||
"theme_color": "#1F2234",
|
||||
"shortcuts": [
|
||||
{
|
||||
"name": tpos.name + " - " + LNBITS_SITE_TITLE,
|
||||
"name": tpos.name + " - " + settings.lnbits_site_title,
|
||||
"short_name": tpos.name,
|
||||
"description": tpos.name + " - " + LNBITS_SITE_TITLE,
|
||||
"description": tpos.name + " - " + settings.lnbits_site_title,
|
||||
"url": "/tpos/" + tpos_id,
|
||||
}
|
||||
],
|
||||
|
|
|
@ -12,7 +12,7 @@ from lnbits.core.models import Payment
|
|||
from lnbits.core.services import create_invoice
|
||||
from lnbits.core.views.api import api_payment
|
||||
from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
|
||||
from lnbits.settings import LNBITS_COMMIT
|
||||
from lnbits.settings import settings
|
||||
|
||||
from . import tpos_ext
|
||||
from .crud import create_tpos, delete_tpos, get_tpos, get_tposs
|
||||
|
@ -135,7 +135,7 @@ async def api_tpos_pay_invoice(
|
|||
|
||||
async with httpx.AsyncClient() as client:
|
||||
try:
|
||||
headers = {"user-agent": f"lnbits/tpos commit {LNBITS_COMMIT[:7]}"}
|
||||
headers = {"user-agent": f"lnbits/tpos commit {settings.lnbits_commit[:7]}"}
|
||||
r = await client.get(lnurl, follow_redirects=True, headers=headers)
|
||||
if r.is_error:
|
||||
lnurl_response = {"success": False, "detail": "Error loading"}
|
||||
|
|
|
@ -6,9 +6,9 @@ from typing import Any, List, NamedTuple, Optional
|
|||
import jinja2
|
||||
import shortuuid # type: ignore
|
||||
|
||||
import lnbits.settings as settings
|
||||
from lnbits.jinja2_templating import Jinja2Templates
|
||||
from lnbits.requestvars import g
|
||||
from lnbits.settings import settings
|
||||
|
||||
|
||||
class Extension(NamedTuple):
|
||||
|
@ -26,12 +26,10 @@ class Extension(NamedTuple):
|
|||
|
||||
class ExtensionManager:
|
||||
def __init__(self):
|
||||
self._disabled: List[str] = settings.LNBITS_DISABLED_EXTENSIONS
|
||||
self._admin_only: List[str] = [
|
||||
x.strip(" ") for x in settings.LNBITS_ADMIN_EXTENSIONS
|
||||
]
|
||||
self._disabled: List[str] = settings.lnbits_disabled_extensions
|
||||
self._admin_only: List[str] = settings.lnbits_admin_extensions
|
||||
self._extension_folders: List[str] = [
|
||||
x[1] for x in os.walk(os.path.join(settings.LNBITS_PATH, "extensions"))
|
||||
x[1] for x in os.walk(os.path.join(settings.lnbits_path, "extensions"))
|
||||
][0]
|
||||
|
||||
@property
|
||||
|
@ -47,7 +45,7 @@ class ExtensionManager:
|
|||
try:
|
||||
with open(
|
||||
os.path.join(
|
||||
settings.LNBITS_PATH, "extensions", extension, "config.json"
|
||||
settings.lnbits_path, "extensions", extension, "config.json"
|
||||
)
|
||||
) as json_file:
|
||||
config = json.load(json_file)
|
||||
|
@ -121,7 +119,7 @@ def get_css_vendored(prefer_minified: bool = False) -> List[str]:
|
|||
def get_vendored(ext: str, prefer_minified: bool = False) -> List[str]:
|
||||
paths: List[str] = []
|
||||
for path in glob.glob(
|
||||
os.path.join(settings.LNBITS_PATH, "static/vendor/**"), recursive=True
|
||||
os.path.join(settings.lnbits_path, "static/vendor/**"), recursive=True
|
||||
):
|
||||
if path.endswith(".min" + ext):
|
||||
# path is minified
|
||||
|
@ -147,7 +145,7 @@ def get_vendored(ext: str, prefer_minified: bool = False) -> List[str]:
|
|||
|
||||
|
||||
def url_for_vendored(abspath: str) -> str:
|
||||
return "/" + os.path.relpath(abspath, settings.LNBITS_PATH)
|
||||
return "/" + os.path.relpath(abspath, settings.lnbits_path)
|
||||
|
||||
|
||||
def url_for(endpoint: str, external: Optional[bool] = False, **params: Any) -> str:
|
||||
|
@ -160,27 +158,29 @@ def url_for(endpoint: str, external: Optional[bool] = False, **params: Any) -> s
|
|||
|
||||
|
||||
def template_renderer(additional_folders: List = []) -> Jinja2Templates:
|
||||
|
||||
t = Jinja2Templates(
|
||||
loader=jinja2.FileSystemLoader(
|
||||
["lnbits/templates", "lnbits/core/templates", *additional_folders]
|
||||
)
|
||||
)
|
||||
|
||||
if settings.LNBITS_AD_SPACE:
|
||||
t.env.globals["AD_TITLE"] = settings.LNBITS_AD_SPACE_TITLE
|
||||
t.env.globals["AD_SPACE"] = settings.LNBITS_AD_SPACE
|
||||
t.env.globals["HIDE_API"] = settings.LNBITS_HIDE_API
|
||||
t.env.globals["SITE_TITLE"] = settings.LNBITS_SITE_TITLE
|
||||
t.env.globals["LNBITS_DENOMINATION"] = settings.LNBITS_DENOMINATION
|
||||
t.env.globals["SITE_TAGLINE"] = settings.LNBITS_SITE_TAGLINE
|
||||
t.env.globals["SITE_DESCRIPTION"] = settings.LNBITS_SITE_DESCRIPTION
|
||||
t.env.globals["LNBITS_THEME_OPTIONS"] = settings.LNBITS_THEME_OPTIONS
|
||||
t.env.globals["LNBITS_VERSION"] = settings.LNBITS_COMMIT
|
||||
t.env.globals["EXTENSIONS"] = get_valid_extensions()
|
||||
if settings.LNBITS_CUSTOM_LOGO:
|
||||
t.env.globals["USE_CUSTOM_LOGO"] = settings.LNBITS_CUSTOM_LOGO
|
||||
if settings.lnbits_ad_space_enabled:
|
||||
t.env.globals["AD_SPACE"] = settings.lnbits_ad_space.split(",")
|
||||
t.env.globals["AD_SPACE_TITLE"] = settings.lnbits_ad_space_title
|
||||
|
||||
if settings.DEBUG:
|
||||
t.env.globals["HIDE_API"] = settings.lnbits_hide_api
|
||||
t.env.globals["SITE_TITLE"] = settings.lnbits_site_title
|
||||
t.env.globals["LNBITS_DENOMINATION"] = settings.lnbits_denomination
|
||||
t.env.globals["SITE_TAGLINE"] = settings.lnbits_site_tagline
|
||||
t.env.globals["SITE_DESCRIPTION"] = settings.lnbits_site_description
|
||||
t.env.globals["LNBITS_THEME_OPTIONS"] = settings.lnbits_theme_options
|
||||
t.env.globals["LNBITS_VERSION"] = settings.lnbits_commit
|
||||
t.env.globals["EXTENSIONS"] = get_valid_extensions()
|
||||
if settings.lnbits_custom_logo:
|
||||
t.env.globals["USE_CUSTOM_LOGO"] = settings.lnbits_custom_logo
|
||||
|
||||
if settings.debug:
|
||||
t.env.globals["VENDORED_JS"] = map(url_for_vendored, get_js_vendored())
|
||||
t.env.globals["VENDORED_CSS"] = map(url_for_vendored, get_css_vendored())
|
||||
else:
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
import uvloop
|
||||
|
||||
uvloop.install()
|
||||
|
||||
import multiprocessing as mp
|
||||
import time
|
||||
|
||||
import click
|
||||
import uvicorn
|
||||
|
||||
from lnbits.settings import FORWARDED_ALLOW_IPS, HOST, PORT
|
||||
from lnbits.settings import set_cli_settings, settings
|
||||
|
||||
|
||||
@click.command(
|
||||
|
@ -10,10 +17,12 @@ from lnbits.settings import FORWARDED_ALLOW_IPS, HOST, PORT
|
|||
allow_extra_args=True,
|
||||
)
|
||||
)
|
||||
@click.option("--port", default=PORT, help="Port to listen on")
|
||||
@click.option("--host", default=HOST, help="Host to run LNbits on")
|
||||
@click.option("--port", default=settings.port, help="Port to listen on")
|
||||
@click.option("--host", default=settings.host, help="Host to run LNBits on")
|
||||
@click.option(
|
||||
"--forwarded-allow-ips", default=FORWARDED_ALLOW_IPS, help="Allowed proxy servers"
|
||||
"--forwarded-allow-ips",
|
||||
default=settings.forwarded_allow_ips,
|
||||
help="Allowed proxy servers",
|
||||
)
|
||||
@click.option("--ssl-keyfile", default=None, help="Path to SSL keyfile")
|
||||
@click.option("--ssl-certfile", default=None, help="Path to SSL certificate")
|
||||
|
@ -27,6 +36,9 @@ def main(
|
|||
ssl_certfile: str,
|
||||
):
|
||||
"""Launched with `poetry run lnbits` at root level"""
|
||||
|
||||
set_cli_settings(host=host, port=port, forwarded_allow_ips=forwarded_allow_ips)
|
||||
|
||||
# this beautiful beast parses all command line arguments and passes them to the uvicorn server
|
||||
d = dict()
|
||||
for a in ctx.args:
|
||||
|
@ -41,18 +53,32 @@ def main(
|
|||
else:
|
||||
d[a.strip("--")] = True # argument like --key
|
||||
|
||||
config = uvicorn.Config(
|
||||
"lnbits.__main__:app",
|
||||
port=port,
|
||||
host=host,
|
||||
forwarded_allow_ips=forwarded_allow_ips,
|
||||
ssl_keyfile=ssl_keyfile,
|
||||
ssl_certfile=ssl_certfile,
|
||||
**d
|
||||
)
|
||||
server = uvicorn.Server(config)
|
||||
server.run()
|
||||
while True:
|
||||
config = uvicorn.Config(
|
||||
"lnbits.__main__:app",
|
||||
loop="uvloop",
|
||||
port=port,
|
||||
host=host,
|
||||
forwarded_allow_ips=forwarded_allow_ips,
|
||||
ssl_keyfile=ssl_keyfile,
|
||||
ssl_certfile=ssl_certfile,
|
||||
**d
|
||||
)
|
||||
|
||||
server = uvicorn.Server(config=config)
|
||||
process = mp.Process(target=server.run)
|
||||
process.start()
|
||||
server_restart.wait()
|
||||
server_restart.clear()
|
||||
server.should_exit = True
|
||||
server.force_exit = True
|
||||
time.sleep(3)
|
||||
process.terminate()
|
||||
process.join()
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
server_restart = mp.Event()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
|
@ -1,92 +1,334 @@
|
|||
import importlib
|
||||
import inspect
|
||||
import json
|
||||
import subprocess
|
||||
from os import path
|
||||
from typing import List
|
||||
from sqlite3 import Row
|
||||
from typing import List, Optional
|
||||
|
||||
from environs import Env # type: ignore
|
||||
import httpx
|
||||
from loguru import logger
|
||||
from pydantic import BaseSettings, Field, validator
|
||||
|
||||
env = Env()
|
||||
env.read_env()
|
||||
|
||||
wallets_module = importlib.import_module("lnbits.wallets")
|
||||
wallet_class = getattr(
|
||||
wallets_module, env.str("LNBITS_BACKEND_WALLET_CLASS", default="VoidWallet")
|
||||
)
|
||||
def list_parse_fallback(v):
|
||||
try:
|
||||
return json.loads(v)
|
||||
except Exception:
|
||||
replaced = v.replace(" ", "")
|
||||
if replaced:
|
||||
return replaced.split(",")
|
||||
else:
|
||||
return []
|
||||
|
||||
DEBUG = env.bool("DEBUG", default=False)
|
||||
|
||||
HOST = env.str("HOST", default="127.0.0.1")
|
||||
PORT = env.int("PORT", default=5000)
|
||||
class LNbitsSetings(BaseSettings):
|
||||
def validate(cls, val):
|
||||
if type(val) == str:
|
||||
val = val.split(",") if val else []
|
||||
return val
|
||||
|
||||
FORWARDED_ALLOW_IPS = env.str("FORWARDED_ALLOW_IPS", default="127.0.0.1")
|
||||
class Config:
|
||||
env_file = ".env"
|
||||
env_file_encoding = "utf-8"
|
||||
case_sensitive = False
|
||||
json_loads = list_parse_fallback
|
||||
|
||||
LNBITS_PATH = path.dirname(path.realpath(__file__))
|
||||
LNBITS_DATA_FOLDER = env.str(
|
||||
"LNBITS_DATA_FOLDER", default=path.join(LNBITS_PATH, "data")
|
||||
)
|
||||
LNBITS_DATABASE_URL = env.str("LNBITS_DATABASE_URL", default=None)
|
||||
|
||||
LNBITS_ALLOWED_USERS: List[str] = [
|
||||
x.strip(" ") for x in env.list("LNBITS_ALLOWED_USERS", default=[], subcast=str)
|
||||
]
|
||||
LNBITS_ADMIN_USERS: List[str] = [
|
||||
x.strip(" ") for x in env.list("LNBITS_ADMIN_USERS", default=[], subcast=str)
|
||||
]
|
||||
LNBITS_ADMIN_EXTENSIONS: List[str] = [
|
||||
x.strip(" ") for x in env.list("LNBITS_ADMIN_EXTENSIONS", default=[], subcast=str)
|
||||
]
|
||||
LNBITS_DISABLED_EXTENSIONS: List[str] = [
|
||||
x.strip(" ")
|
||||
for x in env.list("LNBITS_DISABLED_EXTENSIONS", default=[], subcast=str)
|
||||
]
|
||||
class UsersSetings(LNbitsSetings):
|
||||
lnbits_admin_users: List[str] = Field(default=[])
|
||||
lnbits_allowed_users: List[str] = Field(default=[])
|
||||
lnbits_admin_extensions: List[str] = Field(default=[])
|
||||
lnbits_disabled_extensions: List[str] = Field(default=[])
|
||||
|
||||
LNBITS_AD_SPACE_TITLE = env.str(
|
||||
"LNBITS_AD_SPACE_TITLE", default="Optional Advert Space"
|
||||
)
|
||||
LNBITS_AD_SPACE = [x.strip(" ") for x in env.list("LNBITS_AD_SPACE", default=[])]
|
||||
LNBITS_HIDE_API = env.bool("LNBITS_HIDE_API", default=False)
|
||||
LNBITS_SITE_TITLE = env.str("LNBITS_SITE_TITLE", default="LNbits")
|
||||
LNBITS_DENOMINATION = env.str("LNBITS_DENOMINATION", default="sats")
|
||||
LNBITS_SITE_TAGLINE = env.str(
|
||||
"LNBITS_SITE_TAGLINE", default="free and open-source lightning wallet"
|
||||
)
|
||||
LNBITS_SITE_DESCRIPTION = env.str("LNBITS_SITE_DESCRIPTION", default="")
|
||||
LNBITS_THEME_OPTIONS: List[str] = [
|
||||
x.strip(" ")
|
||||
for x in env.list(
|
||||
"LNBITS_THEME_OPTIONS",
|
||||
default="classic, flamingo, mint, salvador, monochrome, autumn",
|
||||
subcast=str,
|
||||
|
||||
class ThemesSetings(LNbitsSetings):
|
||||
lnbits_site_title: str = Field(default="LNbits")
|
||||
lnbits_site_tagline: str = Field(default="free and open-source lightning wallet")
|
||||
lnbits_site_description: str = Field(default=None)
|
||||
lnbits_default_wallet_name: str = Field(default="LNbits wallet")
|
||||
lnbits_theme_options: List[str] = Field(
|
||||
default=["classic", "flamingo", "mint", "salvador", "monochrome", "autumn"]
|
||||
)
|
||||
]
|
||||
LNBITS_CUSTOM_LOGO = env.str("LNBITS_CUSTOM_LOGO", default="")
|
||||
lnbits_custom_logo: str = Field(default=None)
|
||||
lnbits_ad_space_title: str = Field(default="Supported by")
|
||||
lnbits_ad_space: str = Field(
|
||||
default="https://shop.lnbits.com/;/static/images/lnbits-shop-light.png;/static/images/lnbits-shop-dark.png"
|
||||
) # sneaky sneaky
|
||||
lnbits_ad_space_enabled: bool = Field(default=False)
|
||||
|
||||
WALLET = wallet_class()
|
||||
FAKE_WALLET = getattr(wallets_module, "FakeWallet")()
|
||||
DEFAULT_WALLET_NAME = env.str("LNBITS_DEFAULT_WALLET_NAME", default="LNbits wallet")
|
||||
PREFER_SECURE_URLS = env.bool("LNBITS_FORCE_HTTPS", default=True)
|
||||
|
||||
RESERVE_FEE_MIN = env.int("LNBITS_RESERVE_FEE_MIN", default=2000)
|
||||
RESERVE_FEE_PERCENT = env.float("LNBITS_RESERVE_FEE_PERCENT", default=1.0)
|
||||
SERVICE_FEE = env.float("LNBITS_SERVICE_FEE", default=0.0)
|
||||
class OpsSetings(LNbitsSetings):
|
||||
lnbits_force_https: bool = Field(default=False)
|
||||
lnbits_reserve_fee_min: int = Field(default=2000)
|
||||
lnbits_reserve_fee_percent: float = Field(default=1.0)
|
||||
lnbits_service_fee: float = Field(default=0)
|
||||
lnbits_hide_api: bool = Field(default=False)
|
||||
lnbits_denomination: str = Field(default="sats")
|
||||
|
||||
|
||||
class FakeWalletFundingSource(LNbitsSetings):
|
||||
fake_wallet_secret: str = Field(default="ToTheMoon1")
|
||||
|
||||
|
||||
class LNbitsFundingSource(LNbitsSetings):
|
||||
lnbits_endpoint: str = Field(default="https://legend.lnbits.com")
|
||||
lnbits_key: Optional[str] = Field(default=None)
|
||||
|
||||
|
||||
class ClicheFundingSource(LNbitsSetings):
|
||||
cliche_endpoint: Optional[str] = Field(default=None)
|
||||
|
||||
|
||||
class CoreLightningFundingSource(LNbitsSetings):
|
||||
corelightning_rpc: Optional[str] = Field(default=None)
|
||||
|
||||
|
||||
class EclairFundingSource(LNbitsSetings):
|
||||
eclair_url: Optional[str] = Field(default=None)
|
||||
eclair_pass: Optional[str] = Field(default=None)
|
||||
|
||||
|
||||
class LndRestFundingSource(LNbitsSetings):
|
||||
lnd_rest_endpoint: Optional[str] = Field(default=None)
|
||||
lnd_rest_cert: Optional[str] = Field(default=None)
|
||||
lnd_rest_macaroon: Optional[str] = Field(default=None)
|
||||
lnd_rest_macaroon_encrypted: Optional[str] = Field(default=None)
|
||||
lnd_cert: Optional[str] = Field(default=None)
|
||||
lnd_admin_macaroon: Optional[str] = Field(default=None)
|
||||
lnd_invoice_macaroon: Optional[str] = Field(default=None)
|
||||
|
||||
|
||||
class LndGrpcFundingSource(LNbitsSetings):
|
||||
lnd_grpc_endpoint: Optional[str] = Field(default=None)
|
||||
lnd_grpc_cert: Optional[str] = Field(default=None)
|
||||
lnd_grpc_port: Optional[int] = Field(default=None)
|
||||
lnd_grpc_admin_macaroon: Optional[str] = Field(default=None)
|
||||
lnd_grpc_invoice_macaroon: Optional[str] = Field(default=None)
|
||||
lnd_grpc_macaroon: Optional[str] = Field(default=None)
|
||||
lnd_grpc_macaroon_encrypted: Optional[str] = Field(default=None)
|
||||
|
||||
|
||||
class LnPayFundingSource(LNbitsSetings):
|
||||
lnpay_api_endpoint: Optional[str] = Field(default=None)
|
||||
lnpay_api_key: Optional[str] = Field(default=None)
|
||||
lnpay_wallet_key: Optional[str] = Field(default=None)
|
||||
|
||||
|
||||
class LnTxtBotFundingSource(LNbitsSetings):
|
||||
lntxbot_api_endpoint: Optional[str] = Field(default=None)
|
||||
lntxbot_key: Optional[str] = Field(default=None)
|
||||
|
||||
|
||||
class OpenNodeFundingSource(LNbitsSetings):
|
||||
opennode_api_endpoint: Optional[str] = Field(default=None)
|
||||
opennode_key: Optional[str] = Field(default=None)
|
||||
|
||||
|
||||
class SparkFundingSource(LNbitsSetings):
|
||||
spark_url: Optional[str] = Field(default=None)
|
||||
spark_token: Optional[str] = Field(default=None)
|
||||
|
||||
|
||||
class LnTipsFundingSource(LNbitsSetings):
|
||||
lntips_api_endpoint: Optional[str] = Field(default=None)
|
||||
lntips_api_key: Optional[str] = Field(default=None)
|
||||
lntips_admin_key: Optional[str] = Field(default=None)
|
||||
lntips_invoice_key: Optional[str] = Field(default=None)
|
||||
|
||||
|
||||
# todo: must be extracted
|
||||
class BoltzExtensionSettings(LNbitsSetings):
|
||||
boltz_network: str = Field(default="main")
|
||||
boltz_url: str = Field(default="https://boltz.exchange/api")
|
||||
boltz_mempool_space_url: str = Field(default="https://mempool.space")
|
||||
boltz_mempool_space_url_ws: str = Field(default="wss://mempool.space")
|
||||
|
||||
|
||||
class FundingSourcesSetings(
|
||||
FakeWalletFundingSource,
|
||||
LNbitsFundingSource,
|
||||
ClicheFundingSource,
|
||||
CoreLightningFundingSource,
|
||||
EclairFundingSource,
|
||||
LndRestFundingSource,
|
||||
LndGrpcFundingSource,
|
||||
LnPayFundingSource,
|
||||
LnTxtBotFundingSource,
|
||||
OpenNodeFundingSource,
|
||||
SparkFundingSource,
|
||||
LnTipsFundingSource,
|
||||
):
|
||||
lnbits_backend_wallet_class: str = Field(default="VoidWallet")
|
||||
|
||||
|
||||
class EditableSetings(
|
||||
UsersSetings,
|
||||
ThemesSetings,
|
||||
OpsSetings,
|
||||
FundingSourcesSetings,
|
||||
BoltzExtensionSettings,
|
||||
):
|
||||
@validator(
|
||||
"lnbits_admin_users",
|
||||
"lnbits_allowed_users",
|
||||
"lnbits_theme_options",
|
||||
"lnbits_admin_extensions",
|
||||
"lnbits_disabled_extensions",
|
||||
pre=True,
|
||||
)
|
||||
def validate_editable_settings(cls, val):
|
||||
return super().validate(cls, val)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, d: dict):
|
||||
return cls(
|
||||
**{k: v for k, v in d.items() if k in inspect.signature(cls).parameters}
|
||||
)
|
||||
|
||||
|
||||
class EnvSettings(LNbitsSetings):
|
||||
debug: bool = Field(default=False)
|
||||
host: str = Field(default="127.0.0.1")
|
||||
port: int = Field(default=5000)
|
||||
forwarded_allow_ips: str = Field(default="*")
|
||||
lnbits_path: str = Field(default=".")
|
||||
lnbits_commit: str = Field(default="unknown")
|
||||
super_user: str = Field(default="")
|
||||
|
||||
|
||||
class SaaSSettings(LNbitsSetings):
|
||||
lnbits_saas_callback: Optional[str] = Field(default=None)
|
||||
lnbits_saas_secret: Optional[str] = Field(default=None)
|
||||
lnbits_saas_instance_id: Optional[str] = Field(default=None)
|
||||
|
||||
|
||||
class PersistenceSettings(LNbitsSetings):
|
||||
lnbits_data_folder: str = Field(default="./data")
|
||||
lnbits_database_url: str = Field(default=None)
|
||||
|
||||
|
||||
class SuperUserSettings(LNbitsSetings):
|
||||
lnbits_allowed_funding_sources: List[str] = Field(
|
||||
default=[
|
||||
"VoidWallet",
|
||||
"FakeWallet",
|
||||
"CLightningWallet",
|
||||
"LndRestWallet",
|
||||
"LndWallet",
|
||||
"LntxbotWallet",
|
||||
"LNPayWallet",
|
||||
"LNbitsWallet",
|
||||
"OpenNodeWallet",
|
||||
"LnTipsWallet",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
class ReadOnlySettings(
|
||||
EnvSettings, SaaSSettings, PersistenceSettings, SuperUserSettings
|
||||
):
|
||||
lnbits_admin_ui: bool = Field(default=False)
|
||||
|
||||
@validator(
|
||||
"lnbits_allowed_funding_sources",
|
||||
pre=True,
|
||||
)
|
||||
def validate_readonly_settings(cls, val):
|
||||
return super().validate(cls, val)
|
||||
|
||||
@classmethod
|
||||
def readonly_fields(cls):
|
||||
return [f for f in inspect.signature(cls).parameters if not f.startswith("_")]
|
||||
|
||||
|
||||
class Settings(EditableSetings, ReadOnlySettings):
|
||||
@classmethod
|
||||
def from_row(cls, row: Row) -> "Settings":
|
||||
data = dict(row)
|
||||
return cls(**data)
|
||||
|
||||
|
||||
class SuperSettings(EditableSetings):
|
||||
super_user: str
|
||||
|
||||
|
||||
class AdminSettings(EditableSetings):
|
||||
super_user: bool
|
||||
lnbits_allowed_funding_sources: Optional[List[str]]
|
||||
|
||||
|
||||
def set_cli_settings(**kwargs):
|
||||
for key, value in kwargs.items():
|
||||
setattr(settings, key, value)
|
||||
|
||||
|
||||
# set wallet class after settings are loaded
|
||||
def set_wallet_class():
|
||||
wallet_class = getattr(wallets_module, settings.lnbits_backend_wallet_class)
|
||||
global WALLET
|
||||
WALLET = wallet_class()
|
||||
|
||||
|
||||
def get_wallet_class():
|
||||
# wallet_class = getattr(wallets_module, settings.lnbits_backend_wallet_class)
|
||||
return WALLET
|
||||
|
||||
|
||||
def send_admin_user_to_saas():
|
||||
if settings.lnbits_saas_callback:
|
||||
with httpx.Client() as client:
|
||||
headers = {
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
"X-API-KEY": settings.lnbits_saas_secret,
|
||||
}
|
||||
payload = {
|
||||
"instance_id": settings.lnbits_saas_instance_id,
|
||||
"adminuser": settings.super_user,
|
||||
}
|
||||
try:
|
||||
client.post(
|
||||
settings.lnbits_saas_callback,
|
||||
headers=headers,
|
||||
json=payload,
|
||||
)
|
||||
logger.success("sent super_user to saas application")
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"error sending super_user to saas: {settings.lnbits_saas_callback}. Error: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
############### INIT #################
|
||||
|
||||
readonly_variables = ReadOnlySettings.readonly_fields()
|
||||
|
||||
settings = Settings()
|
||||
|
||||
settings.lnbits_path = str(path.dirname(path.realpath(__file__)))
|
||||
|
||||
try:
|
||||
LNBITS_COMMIT = (
|
||||
settings.lnbits_commit = (
|
||||
subprocess.check_output(
|
||||
["git", "-C", LNBITS_PATH, "rev-parse", "HEAD"], stderr=subprocess.DEVNULL
|
||||
["git", "-C", settings.lnbits_path, "rev-parse", "HEAD"],
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
.strip()
|
||||
.decode("ascii")
|
||||
)
|
||||
except:
|
||||
LNBITS_COMMIT = "unknown"
|
||||
settings.lnbits_commit = "docker"
|
||||
|
||||
|
||||
BOLTZ_NETWORK = env.str("BOLTZ_NETWORK", default="main")
|
||||
BOLTZ_URL = env.str("BOLTZ_URL", default="https://boltz.exchange/api")
|
||||
BOLTZ_MEMPOOL_SPACE_URL = env.str(
|
||||
"BOLTZ_MEMPOOL_SPACE_URL", default="https://mempool.space"
|
||||
)
|
||||
BOLTZ_MEMPOOL_SPACE_URL_WS = env.str(
|
||||
"BOLTZ_MEMPOOL_SPACE_URL_WS", default="wss://mempool.space"
|
||||
)
|
||||
# printing enviroment variable for debugging
|
||||
if not settings.lnbits_admin_ui:
|
||||
logger.debug(f"Enviroment Settings:")
|
||||
for key, value in settings.dict(exclude_none=True).items():
|
||||
logger.debug(f"{key}: {value}")
|
||||
|
||||
|
||||
wallets_module = importlib.import_module("lnbits.wallets")
|
||||
FAKE_WALLET = getattr(wallets_module, "FakeWallet")()
|
||||
|
||||
# initialize as fake wallet
|
||||
WALLET = FAKE_WALLET
|
||||
|
|
|
@ -138,6 +138,7 @@ window.LNbits = {
|
|||
user: function (data) {
|
||||
var obj = {
|
||||
id: data.id,
|
||||
admin: data.admin,
|
||||
email: data.email,
|
||||
extensions: data.extensions,
|
||||
wallets: data.wallets
|
||||
|
|
|
@ -177,6 +177,34 @@ Vue.component('lnbits-extension-list', {
|
|||
}
|
||||
})
|
||||
|
||||
Vue.component('lnbits-admin-ui', {
|
||||
data: function () {
|
||||
return {
|
||||
extensions: [],
|
||||
user: null
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<q-list v-if="user && user.admin" dense class="lnbits-drawer__q-list">
|
||||
<q-item-label header>Admin</q-item-label>
|
||||
<q-item clickable tag="a" :href="['/admin?usr=', user.id].join('')">
|
||||
<q-item-section side>
|
||||
<q-icon name="admin_panel_settings" color="grey-5" size="md"></q-icon>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label lines="1" class="text-caption">Manage Server</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
`,
|
||||
|
||||
created: function () {
|
||||
if (window.user) {
|
||||
this.user = LNbits.map.user(window.user)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Vue.component('lnbits-payment-details', {
|
||||
props: ['payment'],
|
||||
data: function () {
|
||||
|
|
|
@ -15,7 +15,7 @@ from lnbits.core.crud import (
|
|||
get_standalone_payment,
|
||||
)
|
||||
from lnbits.core.services import redeem_lnurl_withdraw
|
||||
from lnbits.settings import WALLET
|
||||
from lnbits.settings import get_wallet_class
|
||||
|
||||
from .core import db
|
||||
|
||||
|
@ -79,6 +79,7 @@ async def webhook_handler():
|
|||
"""
|
||||
Returns the webhook_handler for the selected wallet if present. Used by API.
|
||||
"""
|
||||
WALLET = get_wallet_class()
|
||||
handler = getattr(WALLET, "webhook_listener", None)
|
||||
if handler:
|
||||
return await handler()
|
||||
|
@ -108,6 +109,7 @@ async def invoice_listener():
|
|||
|
||||
Called by the app startup sequence.
|
||||
"""
|
||||
WALLET = get_wallet_class()
|
||||
async for checking_id in WALLET.paid_invoices_stream():
|
||||
logger.info("> got a payment notification", checking_id)
|
||||
asyncio.create_task(invoice_callback_dispatcher(checking_id))
|
||||
|
|
|
@ -175,6 +175,7 @@
|
|||
:elevated="$q.screen.lt.md"
|
||||
>
|
||||
<lnbits-wallet-list></lnbits-wallet-list>
|
||||
<lnbits-admin-ui></lnbits-admin-ui>
|
||||
<lnbits-extension-list class="q-pb-xl"></lnbits-extension-list>
|
||||
</q-drawer>
|
||||
{% endblock %} {% block page_container %}
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import asyncio
|
||||
import hashlib
|
||||
import json
|
||||
from os import getenv
|
||||
from typing import AsyncGenerator, Dict, Optional
|
||||
|
||||
import httpx
|
||||
from loguru import logger
|
||||
from websocket import create_connection
|
||||
|
||||
from lnbits.settings import settings
|
||||
|
||||
from .base import (
|
||||
InvoiceResponse,
|
||||
PaymentResponse,
|
||||
|
@ -21,7 +22,7 @@ class ClicheWallet(Wallet):
|
|||
"""https://github.com/fiatjaf/cliche"""
|
||||
|
||||
def __init__(self):
|
||||
self.endpoint = getenv("CLICHE_ENDPOINT")
|
||||
self.endpoint = settings.cliche_endpoint
|
||||
|
||||
async def status(self) -> StatusResponse:
|
||||
try:
|
||||
|
|
|
@ -8,12 +8,12 @@ import hashlib
|
|||
import random
|
||||
import time
|
||||
from functools import partial, wraps
|
||||
from os import getenv
|
||||
from typing import AsyncGenerator, Optional
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from lnbits import bolt11 as lnbits_bolt11
|
||||
from lnbits.settings import settings
|
||||
|
||||
from .base import (
|
||||
InvoiceResponse,
|
||||
|
@ -51,7 +51,7 @@ class CoreLightningWallet(Wallet):
|
|||
"The `pyln-client` library must be installed to use `CoreLightningWallet`."
|
||||
)
|
||||
|
||||
self.rpc = getenv("CORELIGHTNING_RPC") or getenv("CLIGHTNING_RPC")
|
||||
self.rpc = settings.corelightning_rpc or settings.clightning_rpc
|
||||
self.ln = LightningRpc(self.rpc)
|
||||
|
||||
# check if description_hash is supported (from CLN>=v0.11.0)
|
||||
|
|
|
@ -3,7 +3,6 @@ import base64
|
|||
import hashlib
|
||||
import json
|
||||
import urllib.parse
|
||||
from os import getenv
|
||||
from typing import AsyncGenerator, Dict, Optional
|
||||
|
||||
import httpx
|
||||
|
@ -18,6 +17,8 @@ from websockets.exceptions import (
|
|||
ConnectionClosedOK,
|
||||
)
|
||||
|
||||
from lnbits.settings import settings
|
||||
|
||||
from .base import (
|
||||
InvoiceResponse,
|
||||
PaymentResponse,
|
||||
|
@ -37,12 +38,12 @@ class UnknownError(Exception):
|
|||
|
||||
class EclairWallet(Wallet):
|
||||
def __init__(self):
|
||||
url = getenv("ECLAIR_URL")
|
||||
url = settings.eclair_url
|
||||
self.url = url[:-1] if url.endswith("/") else url
|
||||
|
||||
self.ws_url = f"ws://{urllib.parse.urlsplit(self.url).netloc}/ws"
|
||||
|
||||
passw = getenv("ECLAIR_PASS")
|
||||
passw = settings.eclair_pass
|
||||
encodedAuth = base64.b64encode(f":{passw}".encode("utf-8"))
|
||||
auth = str(encodedAuth, "utf-8")
|
||||
self.auth = {"Authorization": f"Basic {auth}"}
|
||||
|
|
|
@ -2,12 +2,12 @@ import asyncio
|
|||
import hashlib
|
||||
import random
|
||||
from datetime import datetime
|
||||
from os import getenv
|
||||
from typing import AsyncGenerator, Dict, Optional
|
||||
|
||||
from environs import Env # type: ignore
|
||||
from loguru import logger
|
||||
|
||||
from lnbits.settings import settings
|
||||
|
||||
from ..bolt11 import Invoice, decode, encode
|
||||
from .base import (
|
||||
InvoiceResponse,
|
||||
|
@ -17,13 +17,10 @@ from .base import (
|
|||
Wallet,
|
||||
)
|
||||
|
||||
env = Env()
|
||||
env.read_env()
|
||||
|
||||
|
||||
class FakeWallet(Wallet):
|
||||
queue: asyncio.Queue = asyncio.Queue(0)
|
||||
secret: str = env.str("FAKE_WALLET_SECTRET", default="ToTheMoon1")
|
||||
secret: str = settings.fake_wallet_secret
|
||||
privkey: str = hashlib.pbkdf2_hmac(
|
||||
"sha256",
|
||||
secret.encode("utf-8"),
|
||||
|
@ -45,9 +42,6 @@ class FakeWallet(Wallet):
|
|||
description_hash: Optional[bytes] = None,
|
||||
unhashed_description: Optional[bytes] = None,
|
||||
) -> InvoiceResponse:
|
||||
# we set a default secret since FakeWallet is used for internal=True invoices
|
||||
# and the user might not have configured a secret yet
|
||||
|
||||
data: Dict = {
|
||||
"out": False,
|
||||
"amount": amount,
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import asyncio
|
||||
import hashlib
|
||||
import json
|
||||
from os import getenv
|
||||
from typing import AsyncGenerator, Dict, Optional
|
||||
|
||||
import httpx
|
||||
from loguru import logger
|
||||
|
||||
from lnbits.settings import settings
|
||||
|
||||
from .base import (
|
||||
InvoiceResponse,
|
||||
PaymentResponse,
|
||||
|
@ -20,12 +21,12 @@ class LNbitsWallet(Wallet):
|
|||
"""https://github.com/lnbits/lnbits"""
|
||||
|
||||
def __init__(self):
|
||||
self.endpoint = getenv("LNBITS_ENDPOINT")
|
||||
self.endpoint = settings.lnbits_endpoint
|
||||
|
||||
key = (
|
||||
getenv("LNBITS_KEY")
|
||||
or getenv("LNBITS_ADMIN_KEY")
|
||||
or getenv("LNBITS_INVOICE_KEY")
|
||||
settings.lnbits_key
|
||||
or settings.lnbits_admin_key
|
||||
or settings.lnbits_invoice_key
|
||||
)
|
||||
self.key = {"X-Api-Key": key}
|
||||
|
||||
|
@ -147,18 +148,26 @@ class LNbitsWallet(Wallet):
|
|||
while True:
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=None, headers=self.key) as client:
|
||||
async with client.stream("GET", url) as r:
|
||||
del client.headers[
|
||||
"accept-encoding"
|
||||
] # we have to disable compression for SSEs
|
||||
async with client.stream(
|
||||
"GET", url, content="text/event-stream"
|
||||
) as r:
|
||||
sse_trigger = False
|
||||
async for line in r.aiter_lines():
|
||||
if line.startswith("data:"):
|
||||
try:
|
||||
data = json.loads(line[5:])
|
||||
except json.decoder.JSONDecodeError:
|
||||
continue
|
||||
|
||||
if type(data) is not dict:
|
||||
continue
|
||||
|
||||
yield data["payment_hash"] # payment_hash
|
||||
# The data we want to listen to is of this shape:
|
||||
# event: payment-received
|
||||
# data: {.., "payment_hash" : "asd"}
|
||||
if line.startswith("event: payment-received"):
|
||||
sse_trigger = True
|
||||
continue
|
||||
elif sse_trigger and line.startswith("data:"):
|
||||
data = json.loads(line[len("data:") :])
|
||||
sse_trigger = False
|
||||
yield data["payment_hash"]
|
||||
else:
|
||||
sse_trigger = False
|
||||
|
||||
except (OSError, httpx.ReadError, httpx.ConnectError, httpx.ReadTimeout):
|
||||
pass
|
||||
|
|
|
@ -10,7 +10,7 @@ import asyncio
|
|||
import base64
|
||||
import binascii
|
||||
import hashlib
|
||||
from os import environ, error, getenv
|
||||
from os import environ, error
|
||||
from typing import AsyncGenerator, Dict, Optional
|
||||
|
||||
from loguru import logger
|
||||
|
@ -23,6 +23,8 @@ if imports_ok:
|
|||
import lnbits.wallets.lnd_grpc_files.router_pb2 as router
|
||||
import lnbits.wallets.lnd_grpc_files.router_pb2_grpc as routerrpc
|
||||
|
||||
from lnbits.settings import settings
|
||||
|
||||
from .base import (
|
||||
InvoiceResponse,
|
||||
PaymentResponse,
|
||||
|
@ -104,20 +106,20 @@ class LndWallet(Wallet):
|
|||
"The `grpcio` and `protobuf` library must be installed to use `GRPC LndWallet`. Alternatively try using the LndRESTWallet."
|
||||
)
|
||||
|
||||
endpoint = getenv("LND_GRPC_ENDPOINT")
|
||||
endpoint = settings.lnd_grpc_endpoint
|
||||
self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint
|
||||
self.port = int(getenv("LND_GRPC_PORT"))
|
||||
self.cert_path = getenv("LND_GRPC_CERT") or getenv("LND_CERT")
|
||||
self.port = int(settings.lnd_grpc_port)
|
||||
self.cert_path = settings.lnd_grpc_cert or settings.lnd_cert
|
||||
|
||||
macaroon = (
|
||||
getenv("LND_GRPC_MACAROON")
|
||||
or getenv("LND_GRPC_ADMIN_MACAROON")
|
||||
or getenv("LND_ADMIN_MACAROON")
|
||||
or getenv("LND_GRPC_INVOICE_MACAROON")
|
||||
or getenv("LND_INVOICE_MACAROON")
|
||||
settings.lnd_grpc_macaroon
|
||||
or settings.lnd_grpc_admin_macaroon
|
||||
or settings.lnd_admin_macaroon
|
||||
or settings.lnd_grpc_invoice_macaroon
|
||||
or settings.lnd_invoice_macaroon
|
||||
)
|
||||
|
||||
encrypted_macaroon = getenv("LND_GRPC_MACAROON_ENCRYPTED")
|
||||
encrypted_macaroon = settings.lnd_grpc_macaroon_encrypted
|
||||
if encrypted_macaroon:
|
||||
macaroon = AESCipher(description="macaroon decryption").decrypt(
|
||||
encrypted_macaroon
|
||||
|
|
|
@ -2,7 +2,6 @@ import asyncio
|
|||
import base64
|
||||
import hashlib
|
||||
import json
|
||||
from os import getenv
|
||||
from pydoc import describe
|
||||
from typing import AsyncGenerator, Dict, Optional
|
||||
|
||||
|
@ -10,6 +9,7 @@ import httpx
|
|||
from loguru import logger
|
||||
|
||||
from lnbits import bolt11 as lnbits_bolt11
|
||||
from lnbits.settings import settings
|
||||
|
||||
from .base import (
|
||||
InvoiceResponse,
|
||||
|
@ -25,7 +25,7 @@ class LndRestWallet(Wallet):
|
|||
"""https://api.lightning.community/rest/index.html#lnd-rest-api-reference"""
|
||||
|
||||
def __init__(self):
|
||||
endpoint = getenv("LND_REST_ENDPOINT")
|
||||
endpoint = settings.lnd_rest_endpoint
|
||||
endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint
|
||||
endpoint = (
|
||||
"https://" + endpoint if not endpoint.startswith("http") else endpoint
|
||||
|
@ -33,14 +33,14 @@ class LndRestWallet(Wallet):
|
|||
self.endpoint = endpoint
|
||||
|
||||
macaroon = (
|
||||
getenv("LND_REST_MACAROON")
|
||||
or getenv("LND_ADMIN_MACAROON")
|
||||
or getenv("LND_REST_ADMIN_MACAROON")
|
||||
or getenv("LND_INVOICE_MACAROON")
|
||||
or getenv("LND_REST_INVOICE_MACAROON")
|
||||
settings.lnd_rest_macaroon
|
||||
or settings.lnd_admin_macaroon
|
||||
or settings.lnd_rest_admin_macaroon
|
||||
or settings.lnd_invoice_macaroon
|
||||
or settings.lnd_rest_invoice_macaroon
|
||||
)
|
||||
|
||||
encrypted_macaroon = getenv("LND_REST_MACAROON_ENCRYPTED")
|
||||
encrypted_macaroon = settings.lnd_rest_macaroon_encrypted
|
||||
if encrypted_macaroon:
|
||||
macaroon = AESCipher(description="macaroon decryption").decrypt(
|
||||
encrypted_macaroon
|
||||
|
@ -48,7 +48,7 @@ class LndRestWallet(Wallet):
|
|||
self.macaroon = load_macaroon(macaroon)
|
||||
|
||||
self.auth = {"Grpc-Metadata-macaroon": self.macaroon}
|
||||
self.cert = getenv("LND_REST_CERT", True)
|
||||
self.cert = settings.lnd_rest_cert
|
||||
|
||||
async def status(self) -> StatusResponse:
|
||||
try:
|
||||
|
|
|
@ -2,13 +2,14 @@ import asyncio
|
|||
import hashlib
|
||||
import json
|
||||
from http import HTTPStatus
|
||||
from os import getenv
|
||||
from typing import AsyncGenerator, Dict, Optional
|
||||
|
||||
import httpx
|
||||
from fastapi.exceptions import HTTPException
|
||||
from loguru import logger
|
||||
|
||||
from lnbits.settings import settings
|
||||
|
||||
from .base import (
|
||||
InvoiceResponse,
|
||||
PaymentResponse,
|
||||
|
@ -22,10 +23,10 @@ class LNPayWallet(Wallet):
|
|||
"""https://docs.lnpay.co/"""
|
||||
|
||||
def __init__(self):
|
||||
endpoint = getenv("LNPAY_API_ENDPOINT", "https://lnpay.co/v1")
|
||||
endpoint = settings.lnpay_api_endpoint
|
||||
self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint
|
||||
self.wallet_key = getenv("LNPAY_WALLET_KEY") or getenv("LNPAY_ADMIN_KEY")
|
||||
self.auth = {"X-Api-Key": getenv("LNPAY_API_KEY")}
|
||||
self.wallet_key = settings.lnpay_wallet_key or settings.lnpay_admin_key
|
||||
self.auth = {"X-Api-Key": settings.lnpay_api_key}
|
||||
|
||||
async def status(self) -> StatusResponse:
|
||||
url = f"{self.endpoint}/wallet/{self.wallet_key}"
|
||||
|
|
|
@ -2,12 +2,13 @@ import asyncio
|
|||
import hashlib
|
||||
import json
|
||||
import time
|
||||
from os import getenv
|
||||
from typing import AsyncGenerator, Dict, Optional
|
||||
|
||||
import httpx
|
||||
from loguru import logger
|
||||
|
||||
from lnbits.settings import settings
|
||||
|
||||
from .base import (
|
||||
InvoiceResponse,
|
||||
PaymentResponse,
|
||||
|
@ -19,13 +20,13 @@ from .base import (
|
|||
|
||||
class LnTipsWallet(Wallet):
|
||||
def __init__(self):
|
||||
endpoint = getenv("LNTIPS_API_ENDPOINT")
|
||||
endpoint = settings.lntips_api_endpoint
|
||||
self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint
|
||||
|
||||
key = (
|
||||
getenv("LNTIPS_API_KEY")
|
||||
or getenv("LNTIPS_ADMIN_KEY")
|
||||
or getenv("LNTIPS_INVOICE_KEY")
|
||||
settings.lntips_api_key
|
||||
or settings.lntips_admin_key
|
||||
or settings.lntips_invoice_key
|
||||
)
|
||||
self.auth = {"Authorization": f"Basic {key}"}
|
||||
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import asyncio
|
||||
import hashlib
|
||||
import json
|
||||
from os import getenv
|
||||
from typing import AsyncGenerator, Dict, Optional
|
||||
|
||||
import httpx
|
||||
from loguru import logger
|
||||
|
||||
from lnbits.settings import settings
|
||||
|
||||
from .base import (
|
||||
InvoiceResponse,
|
||||
PaymentResponse,
|
||||
|
@ -20,13 +21,13 @@ class LntxbotWallet(Wallet):
|
|||
"""https://github.com/fiatjaf/lntxbot/blob/master/api.go"""
|
||||
|
||||
def __init__(self):
|
||||
endpoint = getenv("LNTXBOT_API_ENDPOINT")
|
||||
endpoint = settings.lntxbot_api_endpoint
|
||||
self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint
|
||||
|
||||
key = (
|
||||
getenv("LNTXBOT_KEY")
|
||||
or getenv("LNTXBOT_ADMIN_KEY")
|
||||
or getenv("LNTXBOT_INVOICE_KEY")
|
||||
settings.lntxbot_key
|
||||
or settings.lntxbot_admin_key
|
||||
or settings.lntxbot_invoice_key
|
||||
)
|
||||
self.auth = {"Authorization": f"Basic {key}"}
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import asyncio
|
||||
import hmac
|
||||
from http import HTTPStatus
|
||||
from os import getenv
|
||||
from typing import AsyncGenerator, Optional
|
||||
|
||||
import httpx
|
||||
|
@ -9,6 +8,7 @@ from fastapi.exceptions import HTTPException
|
|||
from loguru import logger
|
||||
|
||||
from lnbits.helpers import url_for
|
||||
from lnbits.settings import settings
|
||||
|
||||
from .base import (
|
||||
InvoiceResponse,
|
||||
|
@ -24,13 +24,13 @@ class OpenNodeWallet(Wallet):
|
|||
"""https://developers.opennode.com/"""
|
||||
|
||||
def __init__(self):
|
||||
endpoint = getenv("OPENNODE_API_ENDPOINT")
|
||||
endpoint = settings.opennode_api_endpoint
|
||||
self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint
|
||||
|
||||
key = (
|
||||
getenv("OPENNODE_KEY")
|
||||
or getenv("OPENNODE_ADMIN_KEY")
|
||||
or getenv("OPENNODE_INVOICE_KEY")
|
||||
settings.opennode_key
|
||||
or settings.opennode_admin_key
|
||||
or settings.opennode_invoice_key
|
||||
)
|
||||
self.auth = {"Authorization": key}
|
||||
|
||||
|
|
|
@ -2,12 +2,13 @@ import asyncio
|
|||
import hashlib
|
||||
import json
|
||||
import random
|
||||
from os import getenv
|
||||
from typing import AsyncGenerator, Optional
|
||||
|
||||
import httpx
|
||||
from loguru import logger
|
||||
|
||||
from lnbits.settings import settings
|
||||
|
||||
from .base import (
|
||||
InvoiceResponse,
|
||||
PaymentResponse,
|
||||
|
@ -27,8 +28,8 @@ class UnknownError(Exception):
|
|||
|
||||
class SparkWallet(Wallet):
|
||||
def __init__(self):
|
||||
self.url = getenv("SPARK_URL").replace("/rpc", "")
|
||||
self.token = getenv("SPARK_TOKEN")
|
||||
self.url = settings.spark_url.replace("/rpc", "")
|
||||
self.token = settings.spark_token
|
||||
|
||||
def __getattr__(self, key):
|
||||
async def call(*args, **kwargs):
|
||||
|
|
186
poetry.lock
generated
186
poetry.lock
generated
|
@ -69,7 +69,7 @@ python-versions = ">=3.5"
|
|||
dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"]
|
||||
docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"]
|
||||
tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"]
|
||||
tests_no_zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"]
|
||||
tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"]
|
||||
|
||||
[[package]]
|
||||
name = "base58"
|
||||
|
@ -176,7 +176,7 @@ win32-setctime = {version = "1.1.0", markers = "python_version >= \"3.7\" and py
|
|||
zipp = {version = "3.9.0", markers = "python_version >= \"3.7\" and python_version < \"3.8\""}
|
||||
|
||||
[[package]]
|
||||
name = "Cerberus"
|
||||
name = "cerberus"
|
||||
version = "1.3.4"
|
||||
description = "Lightweight, extensible schema and data validation tool for Python dictionaries."
|
||||
category = "main"
|
||||
|
@ -214,7 +214,7 @@ optional = false
|
|||
python-versions = ">=3.5.0"
|
||||
|
||||
[package.extras]
|
||||
unicode_backport = ["unicodedata2"]
|
||||
unicode-backport = ["unicodedata2"]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
|
@ -350,17 +350,14 @@ test = ["anyio[trio] (>=3.2.1,<4.0.0)", "black (==22.3.0)", "databases[sqlite] (
|
|||
|
||||
[[package]]
|
||||
name = "grpcio"
|
||||
version = "1.50.0"
|
||||
version = "1.51.1"
|
||||
description = "HTTP/2-based RPC framework"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
six = ">=1.5.2"
|
||||
|
||||
[package.extras]
|
||||
protobuf = ["grpcio-tools (>=1.50.0)"]
|
||||
protobuf = ["grpcio-tools (>=1.51.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
|
@ -462,12 +459,12 @@ python-versions = ">=3.6.1,<4.0"
|
|||
|
||||
[package.extras]
|
||||
colors = ["colorama (>=0.4.3,<0.5.0)"]
|
||||
pipfile_deprecated_finder = ["pipreqs", "requirementslib"]
|
||||
pipfile-deprecated-finder = ["pipreqs", "requirementslib"]
|
||||
plugins = ["setuptools"]
|
||||
requirements_deprecated_finder = ["pip-api", "pipreqs"]
|
||||
requirements-deprecated-finder = ["pip-api", "pipreqs"]
|
||||
|
||||
[[package]]
|
||||
name = "Jinja2"
|
||||
name = "jinja2"
|
||||
version = "3.0.1"
|
||||
description = "A very fast and expressive template engine."
|
||||
category = "main"
|
||||
|
@ -509,7 +506,7 @@ win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""}
|
|||
dev = ["Sphinx (>=4.1.1)", "black (>=19.10b0)", "colorama (>=0.3.4)", "docutils (==0.16)", "flake8 (>=3.7.7)", "isort (>=5.1.1)", "pytest (>=4.6.2)", "pytest-cov (>=2.7.1)", "sphinx-autobuild (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "tox (>=3.9.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "MarkupSafe"
|
||||
name = "markupsafe"
|
||||
version = "2.0.1"
|
||||
description = "Safely add untrusted strings to HTML/XML markup."
|
||||
category = "main"
|
||||
|
@ -643,7 +640,7 @@ testing = ["pytest", "pytest-benchmark"]
|
|||
|
||||
[[package]]
|
||||
name = "protobuf"
|
||||
version = "4.21.9"
|
||||
version = "4.21.10"
|
||||
description = ""
|
||||
category = "main"
|
||||
optional = false
|
||||
|
@ -751,7 +748,7 @@ optional = false
|
|||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "PyQRCode"
|
||||
name = "pyqrcode"
|
||||
version = "1.2.1"
|
||||
description = "A QR code generator written purely in Python with SVG, EPS, PNG and terminal output."
|
||||
category = "main"
|
||||
|
@ -759,10 +756,10 @@ optional = false
|
|||
python-versions = "*"
|
||||
|
||||
[package.extras]
|
||||
PNG = ["pypng (>=0.0.13)"]
|
||||
png = ["pypng (>=0.0.13)"]
|
||||
|
||||
[[package]]
|
||||
name = "pyScss"
|
||||
name = "pyscss"
|
||||
version = "1.4.0"
|
||||
description = "pyScss, a Scss compiler for Python"
|
||||
category = "main"
|
||||
|
@ -775,7 +772,7 @@ pathlib2 = "*"
|
|||
six = "*"
|
||||
|
||||
[[package]]
|
||||
name = "PySocks"
|
||||
name = "pysocks"
|
||||
version = "1.7.1"
|
||||
description = "A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information."
|
||||
category = "main"
|
||||
|
@ -853,7 +850,7 @@ python-versions = ">=3.7"
|
|||
cli = ["click (>=5.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "PyYAML"
|
||||
name = "pyyaml"
|
||||
version = "5.4.1"
|
||||
description = "YAML parser and emitter for Python"
|
||||
category = "main"
|
||||
|
@ -861,7 +858,7 @@ optional = false
|
|||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
|
||||
|
||||
[[package]]
|
||||
name = "Represent"
|
||||
name = "represent"
|
||||
version = "1.6.0.post0"
|
||||
description = "Create __repr__ automatically or declaratively."
|
||||
category = "main"
|
||||
|
@ -890,7 +887,7 @@ urllib3 = ">=1.21.1,<1.27"
|
|||
|
||||
[package.extras]
|
||||
socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
|
||||
use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"]
|
||||
use-chardet-on-py3 = ["chardet (>=3.0.2,<5)"]
|
||||
|
||||
[[package]]
|
||||
name = "rfc3986"
|
||||
|
@ -955,7 +952,7 @@ optional = false
|
|||
python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "SQLAlchemy"
|
||||
name = "sqlalchemy"
|
||||
version = "1.3.24"
|
||||
description = "Database Abstraction Library"
|
||||
category = "main"
|
||||
|
@ -964,14 +961,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
|||
|
||||
[package.extras]
|
||||
mssql = ["pyodbc"]
|
||||
mssql_pymssql = ["pymssql"]
|
||||
mssql_pyodbc = ["pyodbc"]
|
||||
mssql-pymssql = ["pymssql"]
|
||||
mssql-pyodbc = ["pyodbc"]
|
||||
mysql = ["mysqlclient"]
|
||||
oracle = ["cx_oracle"]
|
||||
postgresql = ["psycopg2"]
|
||||
postgresql_pg8000 = ["pg8000 (<1.16.6)"]
|
||||
postgresql_psycopg2binary = ["psycopg2-binary"]
|
||||
postgresql_psycopg2cffi = ["psycopg2cffi"]
|
||||
postgresql-pg8000 = ["pg8000 (<1.16.6)"]
|
||||
postgresql-psycopg2binary = ["psycopg2-binary"]
|
||||
postgresql-psycopg2cffi = ["psycopg2cffi"]
|
||||
pymysql = ["pymysql", "pymysql (<1)"]
|
||||
|
||||
[[package]]
|
||||
|
@ -1146,6 +1143,7 @@ lock-version = "1.1"
|
|||
python-versions = "^3.10 | ^3.9 | ^3.8 | ^3.7"
|
||||
content-hash = "7f75ca0b067a11f19520dc2121f0789e16738b573a8da84ba3838ed8a466a6e1"
|
||||
|
||||
|
||||
[metadata.files]
|
||||
aiofiles = [
|
||||
{file = "aiofiles-0.8.0-py3-none-any.whl", hash = "sha256:7a973fc22b29e9962d0897805ace5856e6a566ab1f0c8e5c91ff6c866519c937"},
|
||||
|
@ -1211,7 +1209,7 @@ cashu = [
|
|||
{file = "cashu-0.6.0-py3-none-any.whl", hash = "sha256:54096af145643aab45943b235f95a3357b0ec697835c1411e66523049ffb81f6"},
|
||||
{file = "cashu-0.6.0.tar.gz", hash = "sha256:503a90c4ca8d25d0b2c3f78a11b163c32902a726ea5b58e5337dc00eca8e96ad"},
|
||||
]
|
||||
Cerberus = [
|
||||
cerberus = [
|
||||
{file = "Cerberus-1.3.4.tar.gz", hash = "sha256:d1b21b3954b2498d9a79edf16b3170a3ac1021df88d197dc2ce5928ba519237c"},
|
||||
]
|
||||
certifi = [
|
||||
|
@ -1427,51 +1425,51 @@ fastapi = [
|
|||
{file = "fastapi-0.83.0.tar.gz", hash = "sha256:96eb692350fe13d7a9843c3c87a874f0d45102975257dd224903efd6c0fde3bd"},
|
||||
]
|
||||
grpcio = [
|
||||
{file = "grpcio-1.50.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:906f4d1beb83b3496be91684c47a5d870ee628715227d5d7c54b04a8de802974"},
|
||||
{file = "grpcio-1.50.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:2d9fd6e38b16c4d286a01e1776fdf6c7a4123d99ae8d6b3f0b4a03a34bf6ce45"},
|
||||
{file = "grpcio-1.50.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:4b123fbb7a777a2fedec684ca0b723d85e1d2379b6032a9a9b7851829ed3ca9a"},
|
||||
{file = "grpcio-1.50.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2f77a90ba7b85bfb31329f8eab9d9540da2cf8a302128fb1241d7ea239a5469"},
|
||||
{file = "grpcio-1.50.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eea18a878cffc804506d39c6682d71f6b42ec1c151d21865a95fae743fda500"},
|
||||
{file = "grpcio-1.50.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2b71916fa8f9eb2abd93151fafe12e18cebb302686b924bd4ec39266211da525"},
|
||||
{file = "grpcio-1.50.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:95ce51f7a09491fb3da8cf3935005bff19983b77c4e9437ef77235d787b06842"},
|
||||
{file = "grpcio-1.50.0-cp310-cp310-win32.whl", hash = "sha256:f7025930039a011ed7d7e7ef95a1cb5f516e23c5a6ecc7947259b67bea8e06ca"},
|
||||
{file = "grpcio-1.50.0-cp310-cp310-win_amd64.whl", hash = "sha256:05f7c248e440f538aaad13eee78ef35f0541e73498dd6f832fe284542ac4b298"},
|
||||
{file = "grpcio-1.50.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:ca8a2254ab88482936ce941485c1c20cdeaef0efa71a61dbad171ab6758ec998"},
|
||||
{file = "grpcio-1.50.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:3b611b3de3dfd2c47549ca01abfa9bbb95937eb0ea546ea1d762a335739887be"},
|
||||
{file = "grpcio-1.50.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a4cd8cb09d1bc70b3ea37802be484c5ae5a576108bad14728f2516279165dd7"},
|
||||
{file = "grpcio-1.50.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:156f8009e36780fab48c979c5605eda646065d4695deea4cfcbcfdd06627ddb6"},
|
||||
{file = "grpcio-1.50.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de411d2b030134b642c092e986d21aefb9d26a28bf5a18c47dd08ded411a3bc5"},
|
||||
{file = "grpcio-1.50.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d144ad10eeca4c1d1ce930faa105899f86f5d99cecfe0d7224f3c4c76265c15e"},
|
||||
{file = "grpcio-1.50.0-cp311-cp311-win32.whl", hash = "sha256:92d7635d1059d40d2ec29c8bf5ec58900120b3ce5150ef7414119430a4b2dd5c"},
|
||||
{file = "grpcio-1.50.0-cp311-cp311-win_amd64.whl", hash = "sha256:ce8513aee0af9c159319692bfbf488b718d1793d764798c3d5cff827a09e25ef"},
|
||||
{file = "grpcio-1.50.0-cp37-cp37m-linux_armv7l.whl", hash = "sha256:8e8999a097ad89b30d584c034929f7c0be280cd7851ac23e9067111167dcbf55"},
|
||||
{file = "grpcio-1.50.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:a50a1be449b9e238b9bd43d3857d40edf65df9416dea988929891d92a9f8a778"},
|
||||
{file = "grpcio-1.50.0-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:cf151f97f5f381163912e8952eb5b3afe89dec9ed723d1561d59cabf1e219a35"},
|
||||
{file = "grpcio-1.50.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a23d47f2fc7111869f0ff547f771733661ff2818562b04b9ed674fa208e261f4"},
|
||||
{file = "grpcio-1.50.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84d04dec64cc4ed726d07c5d17b73c343c8ddcd6b59c7199c801d6bbb9d9ed1"},
|
||||
{file = "grpcio-1.50.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:67dd41a31f6fc5c7db097a5c14a3fa588af54736ffc174af4411d34c4f306f68"},
|
||||
{file = "grpcio-1.50.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8d4c8e73bf20fb53fe5a7318e768b9734cf122fe671fcce75654b98ba12dfb75"},
|
||||
{file = "grpcio-1.50.0-cp37-cp37m-win32.whl", hash = "sha256:7489dbb901f4fdf7aec8d3753eadd40839c9085967737606d2c35b43074eea24"},
|
||||
{file = "grpcio-1.50.0-cp37-cp37m-win_amd64.whl", hash = "sha256:531f8b46f3d3db91d9ef285191825d108090856b3bc86a75b7c3930f16ce432f"},
|
||||
{file = "grpcio-1.50.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:d534d169673dd5e6e12fb57cc67664c2641361e1a0885545495e65a7b761b0f4"},
|
||||
{file = "grpcio-1.50.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:1d8d02dbb616c0a9260ce587eb751c9c7dc689bc39efa6a88cc4fa3e9c138a7b"},
|
||||
{file = "grpcio-1.50.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:baab51dcc4f2aecabf4ed1e2f57bceab240987c8b03533f1cef90890e6502067"},
|
||||
{file = "grpcio-1.50.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40838061e24f960b853d7bce85086c8e1b81c6342b1f4c47ff0edd44bbae2722"},
|
||||
{file = "grpcio-1.50.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:931e746d0f75b2a5cff0a1197d21827a3a2f400c06bace036762110f19d3d507"},
|
||||
{file = "grpcio-1.50.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:15f9e6d7f564e8f0776770e6ef32dac172c6f9960c478616c366862933fa08b4"},
|
||||
{file = "grpcio-1.50.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a4c23e54f58e016761b576976da6a34d876420b993f45f66a2bfb00363ecc1f9"},
|
||||
{file = "grpcio-1.50.0-cp38-cp38-win32.whl", hash = "sha256:3e4244c09cc1b65c286d709658c061f12c61c814be0b7030a2d9966ff02611e0"},
|
||||
{file = "grpcio-1.50.0-cp38-cp38-win_amd64.whl", hash = "sha256:8e69aa4e9b7f065f01d3fdcecbe0397895a772d99954bb82eefbb1682d274518"},
|
||||
{file = "grpcio-1.50.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:af98d49e56605a2912cf330b4627e5286243242706c3a9fa0bcec6e6f68646fc"},
|
||||
{file = "grpcio-1.50.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:080b66253f29e1646ac53ef288c12944b131a2829488ac3bac8f52abb4413c0d"},
|
||||
{file = "grpcio-1.50.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:ab5d0e3590f0a16cb88de4a3fa78d10eb66a84ca80901eb2c17c1d2c308c230f"},
|
||||
{file = "grpcio-1.50.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb11464f480e6103c59d558a3875bd84eed6723f0921290325ebe97262ae1347"},
|
||||
{file = "grpcio-1.50.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e07fe0d7ae395897981d16be61f0db9791f482f03fee7d1851fe20ddb4f69c03"},
|
||||
{file = "grpcio-1.50.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d75061367a69808ab2e84c960e9dce54749bcc1e44ad3f85deee3a6c75b4ede9"},
|
||||
{file = "grpcio-1.50.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ae23daa7eda93c1c49a9ecc316e027ceb99adbad750fbd3a56fa9e4a2ffd5ae0"},
|
||||
{file = "grpcio-1.50.0-cp39-cp39-win32.whl", hash = "sha256:177afaa7dba3ab5bfc211a71b90da1b887d441df33732e94e26860b3321434d9"},
|
||||
{file = "grpcio-1.50.0-cp39-cp39-win_amd64.whl", hash = "sha256:ea8ccf95e4c7e20419b7827aa5b6da6f02720270686ac63bd3493a651830235c"},
|
||||
{file = "grpcio-1.50.0.tar.gz", hash = "sha256:12b479839a5e753580b5e6053571de14006157f2ef9b71f38c56dc9b23b95ad6"},
|
||||
{file = "grpcio-1.51.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:cc2bece1737b44d878cc1510ea04469a8073dbbcdd762175168937ae4742dfb3"},
|
||||
{file = "grpcio-1.51.1-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:e223a9793522680beae44671b9ed8f6d25bbe5ddf8887e66aebad5e0686049ef"},
|
||||
{file = "grpcio-1.51.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:24ac1154c4b2ab4a0c5326a76161547e70664cd2c39ba75f00fc8a2170964ea2"},
|
||||
{file = "grpcio-1.51.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4ef09f8997c4be5f3504cefa6b5c6cc3cf648274ce3cede84d4342a35d76db6"},
|
||||
{file = "grpcio-1.51.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8a0b77e992c64880e6efbe0086fe54dfc0bbd56f72a92d9e48264dcd2a3db98"},
|
||||
{file = "grpcio-1.51.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:eacad297ea60c72dd280d3353d93fb1dcca952ec11de6bb3c49d12a572ba31dd"},
|
||||
{file = "grpcio-1.51.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:16c71740640ba3a882f50b01bf58154681d44b51f09a5728180a8fdc66c67bd5"},
|
||||
{file = "grpcio-1.51.1-cp310-cp310-win32.whl", hash = "sha256:29cb97d41a4ead83b7bcad23bdb25bdd170b1e2cba16db6d3acbb090bc2de43c"},
|
||||
{file = "grpcio-1.51.1-cp310-cp310-win_amd64.whl", hash = "sha256:9ff42c5620b4e4530609e11afefa4a62ca91fa0abb045a8957e509ef84e54d30"},
|
||||
{file = "grpcio-1.51.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:bc59f7ba87972ab236f8669d8ca7400f02a0eadf273ca00e02af64d588046f02"},
|
||||
{file = "grpcio-1.51.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:3c2b3842dcf870912da31a503454a33a697392f60c5e2697c91d133130c2c85d"},
|
||||
{file = "grpcio-1.51.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22b011674090594f1f3245960ced7386f6af35485a38901f8afee8ad01541dbd"},
|
||||
{file = "grpcio-1.51.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49d680356a975d9c66a678eb2dde192d5dc427a7994fb977363634e781614f7c"},
|
||||
{file = "grpcio-1.51.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:094e64236253590d9d4075665c77b329d707b6fca864dd62b144255e199b4f87"},
|
||||
{file = "grpcio-1.51.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:257478300735ce3c98d65a930bbda3db172bd4e00968ba743e6a1154ea6edf10"},
|
||||
{file = "grpcio-1.51.1-cp311-cp311-win32.whl", hash = "sha256:5a6ebcdef0ef12005d56d38be30f5156d1cb3373b52e96f147f4a24b0ddb3a9d"},
|
||||
{file = "grpcio-1.51.1-cp311-cp311-win_amd64.whl", hash = "sha256:3f9b0023c2c92bebd1be72cdfca23004ea748be1813a66d684d49d67d836adde"},
|
||||
{file = "grpcio-1.51.1-cp37-cp37m-linux_armv7l.whl", hash = "sha256:cd3baccea2bc5c38aeb14e5b00167bd4e2373a373a5e4d8d850bd193edad150c"},
|
||||
{file = "grpcio-1.51.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:17ec9b13cec4a286b9e606b48191e560ca2f3bbdf3986f91e480a95d1582e1a7"},
|
||||
{file = "grpcio-1.51.1-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:fbdbe9a849854fe484c00823f45b7baab159bdd4a46075302281998cb8719df5"},
|
||||
{file = "grpcio-1.51.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:31bb6bc7ff145e2771c9baf612f4b9ebbc9605ccdc5f3ff3d5553de7fc0e0d79"},
|
||||
{file = "grpcio-1.51.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e473525c28251558337b5c1ad3fa969511e42304524a4e404065e165b084c9e4"},
|
||||
{file = "grpcio-1.51.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6f0b89967ee11f2b654c23b27086d88ad7bf08c0b3c2a280362f28c3698b2896"},
|
||||
{file = "grpcio-1.51.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7942b32a291421460d6a07883033e392167d30724aa84987e6956cd15f1a21b9"},
|
||||
{file = "grpcio-1.51.1-cp37-cp37m-win32.whl", hash = "sha256:f96ace1540223f26fbe7c4ebbf8a98e3929a6aa0290c8033d12526847b291c0f"},
|
||||
{file = "grpcio-1.51.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f1fec3abaf274cdb85bf3878167cfde5ad4a4d97c68421afda95174de85ba813"},
|
||||
{file = "grpcio-1.51.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:0e1a9e1b4a23808f1132aa35f968cd8e659f60af3ffd6fb00bcf9a65e7db279f"},
|
||||
{file = "grpcio-1.51.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:6df3b63538c362312bc5fa95fb965069c65c3ea91d7ce78ad9c47cab57226f54"},
|
||||
{file = "grpcio-1.51.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:172405ca6bdfedd6054c74c62085946e45ad4d9cec9f3c42b4c9a02546c4c7e9"},
|
||||
{file = "grpcio-1.51.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:506b9b7a4cede87d7219bfb31014d7b471cfc77157da9e820a737ec1ea4b0663"},
|
||||
{file = "grpcio-1.51.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fb93051331acbb75b49a2a0fd9239c6ba9528f6bdc1dd400ad1cb66cf864292"},
|
||||
{file = "grpcio-1.51.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5dca372268c6ab6372d37d6b9f9343e7e5b4bc09779f819f9470cd88b2ece3c3"},
|
||||
{file = "grpcio-1.51.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:471d39d3370ca923a316d49c8aac66356cea708a11e647e3bdc3d0b5de4f0a40"},
|
||||
{file = "grpcio-1.51.1-cp38-cp38-win32.whl", hash = "sha256:75e29a90dc319f0ad4d87ba6d20083615a00d8276b51512e04ad7452b5c23b04"},
|
||||
{file = "grpcio-1.51.1-cp38-cp38-win_amd64.whl", hash = "sha256:f1158bccbb919da42544a4d3af5d9296a3358539ffa01018307337365a9a0c64"},
|
||||
{file = "grpcio-1.51.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:59dffade859f157bcc55243714d57b286da6ae16469bf1ac0614d281b5f49b67"},
|
||||
{file = "grpcio-1.51.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:dad6533411d033b77f5369eafe87af8583178efd4039c41d7515d3336c53b4f1"},
|
||||
{file = "grpcio-1.51.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:4c4423ea38a7825b8fed8934d6d9aeebdf646c97e3c608c3b0bcf23616f33877"},
|
||||
{file = "grpcio-1.51.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0dc5354e38e5adf2498312f7241b14c7ce3484eefa0082db4297189dcbe272e6"},
|
||||
{file = "grpcio-1.51.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97d67983189e2e45550eac194d6234fc38b8c3b5396c153821f2d906ed46e0ce"},
|
||||
{file = "grpcio-1.51.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:538d981818e49b6ed1e9c8d5e5adf29f71c4e334e7d459bf47e9b7abb3c30e09"},
|
||||
{file = "grpcio-1.51.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9235dcd5144a83f9ca6f431bd0eccc46b90e2c22fe27b7f7d77cabb2fb515595"},
|
||||
{file = "grpcio-1.51.1-cp39-cp39-win32.whl", hash = "sha256:aacb54f7789ede5cbf1d007637f792d3e87f1c9841f57dd51abf89337d1b8472"},
|
||||
{file = "grpcio-1.51.1-cp39-cp39-win_amd64.whl", hash = "sha256:2b170eaf51518275c9b6b22ccb59450537c5a8555326fd96ff7391b5dd75303c"},
|
||||
{file = "grpcio-1.51.1.tar.gz", hash = "sha256:e6dfc2b6567b1c261739b43d9c59d201c1b89e017afd9e684d85aa7a186c9f7a"},
|
||||
]
|
||||
h11 = [
|
||||
{file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6"},
|
||||
|
@ -1537,7 +1535,7 @@ isort = [
|
|||
{file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"},
|
||||
{file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"},
|
||||
]
|
||||
Jinja2 = [
|
||||
jinja2 = [
|
||||
{file = "Jinja2-3.0.1-py3-none-any.whl", hash = "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4"},
|
||||
{file = "Jinja2-3.0.1.tar.gz", hash = "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"},
|
||||
]
|
||||
|
@ -1549,7 +1547,7 @@ loguru = [
|
|||
{file = "loguru-0.6.0-py3-none-any.whl", hash = "sha256:4e2414d534a2ab57573365b3e6d0234dfb1d84b68b7f3b948e6fb743860a77c3"},
|
||||
{file = "loguru-0.6.0.tar.gz", hash = "sha256:066bd06758d0a513e9836fd9c6b5a75bfb3fd36841f4b996bc60b547a309d41c"},
|
||||
]
|
||||
MarkupSafe = [
|
||||
markupsafe = [
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"},
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"},
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"},
|
||||
|
@ -1682,20 +1680,20 @@ pluggy = [
|
|||
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
|
||||
]
|
||||
protobuf = [
|
||||
{file = "protobuf-4.21.9-cp310-abi3-win32.whl", hash = "sha256:6e0be9f09bf9b6cf497b27425487706fa48c6d1632ddd94dab1a5fe11a422392"},
|
||||
{file = "protobuf-4.21.9-cp310-abi3-win_amd64.whl", hash = "sha256:a7d0ea43949d45b836234f4ebb5ba0b22e7432d065394b532cdca8f98415e3cf"},
|
||||
{file = "protobuf-4.21.9-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:b5ab0b8918c136345ff045d4b3d5f719b505b7c8af45092d7f45e304f55e50a1"},
|
||||
{file = "protobuf-4.21.9-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:2c9c2ed7466ad565f18668aa4731c535511c5d9a40c6da39524bccf43e441719"},
|
||||
{file = "protobuf-4.21.9-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:e575c57dc8b5b2b2caa436c16d44ef6981f2235eb7179bfc847557886376d740"},
|
||||
{file = "protobuf-4.21.9-cp37-cp37m-win32.whl", hash = "sha256:9227c14010acd9ae7702d6467b4625b6fe853175a6b150e539b21d2b2f2b409c"},
|
||||
{file = "protobuf-4.21.9-cp37-cp37m-win_amd64.whl", hash = "sha256:a419cc95fca8694804709b8c4f2326266d29659b126a93befe210f5bbc772536"},
|
||||
{file = "protobuf-4.21.9-cp38-cp38-win32.whl", hash = "sha256:5b0834e61fb38f34ba8840d7dcb2e5a2f03de0c714e0293b3963b79db26de8ce"},
|
||||
{file = "protobuf-4.21.9-cp38-cp38-win_amd64.whl", hash = "sha256:84ea107016244dfc1eecae7684f7ce13c788b9a644cd3fca5b77871366556444"},
|
||||
{file = "protobuf-4.21.9-cp39-cp39-win32.whl", hash = "sha256:f9eae277dd240ae19bb06ff4e2346e771252b0e619421965504bd1b1bba7c5fa"},
|
||||
{file = "protobuf-4.21.9-cp39-cp39-win_amd64.whl", hash = "sha256:6e312e280fbe3c74ea9e080d9e6080b636798b5e3939242298b591064470b06b"},
|
||||
{file = "protobuf-4.21.9-py2.py3-none-any.whl", hash = "sha256:7eb8f2cc41a34e9c956c256e3ac766cf4e1a4c9c925dc757a41a01be3e852965"},
|
||||
{file = "protobuf-4.21.9-py3-none-any.whl", hash = "sha256:48e2cd6b88c6ed3d5877a3ea40df79d08374088e89bedc32557348848dff250b"},
|
||||
{file = "protobuf-4.21.9.tar.gz", hash = "sha256:61f21493d96d2a77f9ca84fefa105872550ab5ef71d21c458eb80edcf4885a99"},
|
||||
{file = "protobuf-4.21.10-cp310-abi3-win32.whl", hash = "sha256:e92768d17473657c87e98b79a4c7724b0ddfa23211b05ce137bfdc55e734e36f"},
|
||||
{file = "protobuf-4.21.10-cp310-abi3-win_amd64.whl", hash = "sha256:0c968753028cb14b1d24cc839723f7e9505b305fc588a37a9e0f7d270cb59d89"},
|
||||
{file = "protobuf-4.21.10-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:e53165dd14d19abc7f50733f365de431e51d1d262db40c0ee22e271a074fac59"},
|
||||
{file = "protobuf-4.21.10-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:5efa8a8162ada7e10847140308fbf84fdc5b89dc21655d12ec04aed87284fe07"},
|
||||
{file = "protobuf-4.21.10-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:2a172741b5b041a896b621cef4277077afd571e0d3a6e524e7171f1c70e33200"},
|
||||
{file = "protobuf-4.21.10-cp37-cp37m-win32.whl", hash = "sha256:05cbcb9a25cd781fd949f93f6f98a911883868c0360c6d2264fc99a903c8f0d7"},
|
||||
{file = "protobuf-4.21.10-cp37-cp37m-win_amd64.whl", hash = "sha256:3f08f04b4f101dd469efbcc1731fbf48068eccd8a42f4e2ea530aa012a5f56f8"},
|
||||
{file = "protobuf-4.21.10-cp38-cp38-win32.whl", hash = "sha256:6b809f20923b6ef49dc1755cb50bdb21be179b4a3c7ffcab1fe5d3f139b58a51"},
|
||||
{file = "protobuf-4.21.10-cp38-cp38-win_amd64.whl", hash = "sha256:81b233a06c62387ea5c9be2cd9aedd2ba09940e91da53b920e9ff5bd98e48e7f"},
|
||||
{file = "protobuf-4.21.10-cp39-cp39-win32.whl", hash = "sha256:b78d7c2c36b51c0041b9bf000be4adb09f4112bfc40bc7a9d48ac0b0dfad139e"},
|
||||
{file = "protobuf-4.21.10-cp39-cp39-win_amd64.whl", hash = "sha256:0413addc126c40a5440ee59be098de1007183d68e9f5f20ed5fbc44848f417ca"},
|
||||
{file = "protobuf-4.21.10-py2.py3-none-any.whl", hash = "sha256:a5e89eabaa0ca72ce1b7c8104a740d44cdb67942cbbed00c69a4c0541de17107"},
|
||||
{file = "protobuf-4.21.10-py3-none-any.whl", hash = "sha256:5096b3922b45e4b7a04d3d3cb855d13bb5ccd4d5e44b129e706232ebf0ffb870"},
|
||||
{file = "protobuf-4.21.10.tar.gz", hash = "sha256:4d97c16c0d11155b3714a29245461f0eb60cace294455077f3a3b8a629afa383"},
|
||||
]
|
||||
psycopg2-binary = [
|
||||
{file = "psycopg2-binary-2.9.1.tar.gz", hash = "sha256:b0221ca5a9837e040ebf61f48899926b5783668b7807419e4adae8175a31f773"},
|
||||
|
@ -1829,14 +1827,14 @@ pyparsing = [
|
|||
pypng = [
|
||||
{file = "pypng-0.0.21-py3-none-any.whl", hash = "sha256:76f8a1539ec56451da7ab7121f12a361969fe0f2d48d703d198ce2a99d6c5afd"},
|
||||
]
|
||||
PyQRCode = [
|
||||
pyqrcode = [
|
||||
{file = "PyQRCode-1.2.1.tar.gz", hash = "sha256:fdbf7634733e56b72e27f9bce46e4550b75a3a2c420414035cae9d9d26b234d5"},
|
||||
{file = "PyQRCode-1.2.1.zip", hash = "sha256:1b2812775fa6ff5c527977c4cd2ccb07051ca7d0bc0aecf937a43864abe5eff6"},
|
||||
]
|
||||
pyScss = [
|
||||
pyscss = [
|
||||
{file = "pyScss-1.4.0.tar.gz", hash = "sha256:8f35521ffe36afa8b34c7d6f3195088a7057c185c2b8f15ee459ab19748669ff"},
|
||||
]
|
||||
PySocks = [
|
||||
pysocks = [
|
||||
{file = "PySocks-1.7.1-py27-none-any.whl", hash = "sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299"},
|
||||
{file = "PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5"},
|
||||
{file = "PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0"},
|
||||
|
@ -1861,7 +1859,7 @@ python-dotenv = [
|
|||
{file = "python-dotenv-0.21.0.tar.gz", hash = "sha256:b77d08274639e3d34145dfa6c7008e66df0f04b7be7a75fd0d5292c191d79045"},
|
||||
{file = "python_dotenv-0.21.0-py3-none-any.whl", hash = "sha256:1684eb44636dd462b66c3ee016599815514527ad99965de77f43e0944634a7e5"},
|
||||
]
|
||||
PyYAML = [
|
||||
pyyaml = [
|
||||
{file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"},
|
||||
{file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"},
|
||||
{file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"},
|
||||
|
@ -1892,7 +1890,7 @@ PyYAML = [
|
|||
{file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"},
|
||||
{file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"},
|
||||
]
|
||||
Represent = [
|
||||
represent = [
|
||||
{file = "Represent-1.6.0.post0-py2.py3-none-any.whl", hash = "sha256:99142650756ef1998ce0661568f54a47dac8c638fb27e3816c02536575dbba8c"},
|
||||
{file = "Represent-1.6.0.post0.tar.gz", hash = "sha256:026c0de2ee8385d1255b9c2426cd4f03fe9177ac94c09979bc601946c8493aa0"},
|
||||
]
|
||||
|
@ -1945,7 +1943,7 @@ sniffio = [
|
|||
{file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"},
|
||||
{file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"},
|
||||
]
|
||||
SQLAlchemy = [
|
||||
sqlalchemy = [
|
||||
{file = "SQLAlchemy-1.3.24-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:87a2725ad7d41cd7376373c15fd8bf674e9c33ca56d0b8036add2d634dba372e"},
|
||||
{file = "SQLAlchemy-1.3.24-cp27-cp27m-win32.whl", hash = "sha256:f597a243b8550a3a0b15122b14e49d8a7e622ba1c9d29776af741f1845478d79"},
|
||||
{file = "SQLAlchemy-1.3.24-cp27-cp27m-win_amd64.whl", hash = "sha256:fc4cddb0b474b12ed7bdce6be1b9edc65352e8ce66bc10ff8cbbfb3d4047dbf4"},
|
||||
|
|
|
@ -20,7 +20,6 @@ charset-normalizer = "2.0.12"
|
|||
click = "8.0.4"
|
||||
ecdsa = "0.18.0"
|
||||
embit = "0.4.9"
|
||||
environs = "9.5.0"
|
||||
fastapi = "0.83.0"
|
||||
h11 = "0.12.0"
|
||||
httpcore = "0.15.0"
|
||||
|
@ -39,7 +38,6 @@ pydantic = "1.10.2"
|
|||
pypng = "0.0.21"
|
||||
pyqrcode = "1.2.1"
|
||||
pyScss = "1.4.0"
|
||||
python-dotenv = "0.21.0"
|
||||
pyyaml = "5.4.1"
|
||||
represent = "1.6.0.post0"
|
||||
rfc3986 = "1.5.0"
|
||||
|
|
|
@ -10,7 +10,7 @@ from lnbits.core.crud import create_account, create_wallet, get_wallet
|
|||
from lnbits.core.models import BalanceCheck, Payment, User, Wallet
|
||||
from lnbits.core.views.api import CreateInvoiceData, api_payments_create_invoice
|
||||
from lnbits.db import Database
|
||||
from lnbits.settings import HOST, PORT
|
||||
from lnbits.settings import settings
|
||||
from tests.helpers import credit_wallet, get_random_invoice_data
|
||||
|
||||
|
||||
|
@ -38,7 +38,7 @@ def app(event_loop):
|
|||
|
||||
@pytest_asyncio.fixture(scope="session")
|
||||
async def client(app):
|
||||
client = AsyncClient(app=app, base_url=f"http://{HOST}:{PORT}")
|
||||
client = AsyncClient(app=app, base_url=f"http://{settings.host}:{settings.port}")
|
||||
yield client
|
||||
await client.aclose()
|
||||
|
||||
|
|
|
@ -11,10 +11,11 @@ from lnbits.core.views.api import (
|
|||
api_payment,
|
||||
api_payments_create_invoice,
|
||||
)
|
||||
from lnbits.settings import wallet_class
|
||||
from lnbits.settings import get_wallet_class
|
||||
|
||||
from ...helpers import get_random_invoice_data, is_regtest
|
||||
|
||||
WALLET = get_wallet_class()
|
||||
|
||||
# check if the client is working
|
||||
@pytest.mark.asyncio
|
||||
|
@ -209,7 +210,7 @@ async def test_api_payment_with_key(invoice, inkey_headers_from):
|
|||
|
||||
# check POST /api/v1/payments: invoice creation with a description hash
|
||||
@pytest.mark.skipif(
|
||||
wallet_class.__name__ in ["CoreLightningWallet"],
|
||||
WALLET.__class__.__name__ in ["CoreLightningWallet"],
|
||||
reason="wallet does not support description_hash",
|
||||
)
|
||||
@pytest.mark.asyncio
|
||||
|
|
Binary file not shown.
|
@ -9,11 +9,12 @@ from lnbits.extensions.bleskomat.helpers import (
|
|||
generate_bleskomat_lnurl_signature,
|
||||
query_to_signing_payload,
|
||||
)
|
||||
from lnbits.settings import HOST, PORT
|
||||
from lnbits.settings import get_wallet_class, settings
|
||||
from tests.conftest import client
|
||||
from tests.extensions.bleskomat.conftest import bleskomat, lnurl
|
||||
from tests.helpers import credit_wallet, is_regtest
|
||||
from tests.mocks import WALLET
|
||||
|
||||
WALLET = get_wallet_class()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
@ -90,7 +91,7 @@ async def test_bleskomat_lnurl_api_valid_signature(client, bleskomat):
|
|||
assert data["minWithdrawable"] == 1000
|
||||
assert data["maxWithdrawable"] == 1000
|
||||
assert data["defaultDescription"] == "test valid sig"
|
||||
assert data["callback"] == f"http://{HOST}:{PORT}/bleskomat/u"
|
||||
assert data["callback"] == f"http://{settings.host}:{settings.port}/bleskomat/u"
|
||||
k1 = data["k1"]
|
||||
lnurl = await get_bleskomat_lnurl(secret=k1)
|
||||
assert lnurl
|
||||
|
@ -110,8 +111,10 @@ async def test_bleskomat_lnurl_api_action_insufficient_balance(client, lnurl):
|
|||
"fee" in response.json()["reason"]
|
||||
)
|
||||
wallet = await get_wallet(bleskomat.wallet)
|
||||
assert wallet, not None
|
||||
assert wallet.balance_msat == 0
|
||||
bleskomat_lnurl = await get_bleskomat_lnurl(secret)
|
||||
assert bleskomat_lnurl, not None
|
||||
assert bleskomat_lnurl.has_uses_remaining() == True
|
||||
WALLET.pay_invoice.assert_not_called()
|
||||
|
||||
|
@ -127,12 +130,15 @@ async def test_bleskomat_lnurl_api_action_success(client, lnurl):
|
|||
amount=100000,
|
||||
)
|
||||
wallet = await get_wallet(bleskomat.wallet)
|
||||
assert wallet, not None
|
||||
assert wallet.balance_msat == 100000
|
||||
WALLET.pay_invoice.reset_mock()
|
||||
response = await client.get(f"/bleskomat/u?k1={secret}&pr={pr}")
|
||||
assert response.json() == {"status": "OK"}
|
||||
wallet = await get_wallet(bleskomat.wallet)
|
||||
assert wallet, not None
|
||||
assert wallet.balance_msat == 50000
|
||||
bleskomat_lnurl = await get_bleskomat_lnurl(secret)
|
||||
assert bleskomat_lnurl, not None
|
||||
assert bleskomat_lnurl.has_uses_remaining() == False
|
||||
WALLET.pay_invoice.assert_called_once_with(pr, 2000)
|
||||
|
|
|
@ -4,7 +4,7 @@ import secrets
|
|||
import string
|
||||
|
||||
from lnbits.core.crud import create_payment
|
||||
from lnbits.settings import wallet_class
|
||||
from lnbits.settings import get_wallet_class
|
||||
|
||||
|
||||
async def credit_wallet(wallet_id: str, amount: int):
|
||||
|
@ -35,5 +35,6 @@ async def get_random_invoice_data():
|
|||
return {"out": False, "amount": 10, "memo": f"test_memo_{get_random_string(10)}"}
|
||||
|
||||
|
||||
is_fake: bool = wallet_class.__name__ == "FakeWallet"
|
||||
WALLET = get_wallet_class()
|
||||
is_fake: bool = WALLET.__class__.__name__ == "FakeWallet"
|
||||
is_regtest: bool = not is_fake
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
from mock import AsyncMock
|
||||
|
||||
from lnbits import bolt11
|
||||
from lnbits.settings import WALLET
|
||||
from lnbits.wallets.base import PaymentResponse, PaymentStatus, StatusResponse
|
||||
from lnbits.wallets.fake import FakeWallet
|
||||
|
||||
from .helpers import get_random_string, is_fake
|
||||
from .helpers import WALLET, get_random_string, is_fake
|
||||
|
||||
|
||||
# generates an invoice with FakeWallet
|
||||
|
|
|
@ -5,34 +5,29 @@ import sys
|
|||
from typing import List
|
||||
|
||||
import psycopg2
|
||||
from environs import Env # type: ignore
|
||||
|
||||
env = Env()
|
||||
env.read_env()
|
||||
from lnbits.settings import settings
|
||||
|
||||
# Python script to migrate an LNbits SQLite DB to Postgres
|
||||
# All credits to @Fritz446 for the awesome work
|
||||
|
||||
|
||||
# pip install psycopg2 OR psycopg2-binary
|
||||
|
||||
|
||||
# Change these values as needed
|
||||
|
||||
sqfolder = settings.lnbits_data_folder
|
||||
db_url = settings.lnbits_database_url
|
||||
|
||||
sqfolder = env.str("LNBITS_DATA_FOLDER", default=None)
|
||||
|
||||
LNBITS_DATABASE_URL = env.str("LNBITS_DATABASE_URL", default=None)
|
||||
if LNBITS_DATABASE_URL is None:
|
||||
if db_url is None:
|
||||
print("missing LNBITS_DATABASE_URL")
|
||||
sys.exit(1)
|
||||
else:
|
||||
# parse postgres://lnbits:postgres@localhost:5432/lnbits
|
||||
pgdb = LNBITS_DATABASE_URL.split("/")[-1]
|
||||
pguser = LNBITS_DATABASE_URL.split("@")[0].split(":")[-2][2:]
|
||||
pgpswd = LNBITS_DATABASE_URL.split("@")[0].split(":")[-1]
|
||||
pghost = LNBITS_DATABASE_URL.split("@")[1].split(":")[0]
|
||||
pgport = LNBITS_DATABASE_URL.split("@")[1].split(":")[1].split("/")[0]
|
||||
pgdb = db_url.split("/")[-1]
|
||||
pguser = db_url.split("@")[0].split(":")[-2][2:]
|
||||
pgpswd = db_url.split("@")[0].split(":")[-1]
|
||||
pghost = db_url.split("@")[1].split(":")[0]
|
||||
pgport = db_url.split("@")[1].split(":")[1].split("/")[0]
|
||||
pgschema = ""
|
||||
|
||||
|
||||
|
@ -149,7 +144,7 @@ def migrate_db(file: str, schema: str, exclude_tables: List[str] = []):
|
|||
|
||||
|
||||
def build_insert_query(schema, tableName, columns):
|
||||
to_columns = ", ".join(map(lambda column: f'"{column[1]}"', columns))
|
||||
to_columns = ", ".join(map(lambda column: f'"{column[1].lower()}"', columns))
|
||||
values = ", ".join(map(lambda column: to_column_type(column[2]), columns))
|
||||
return f"""
|
||||
INSERT INTO {schema}.{tableName}({to_columns})
|
||||
|
|
Loading…
Reference in New Issue
Block a user