[feat] User notifications backend (#3280)

Co-authored-by: Arc <33088785+arcbtc@users.noreply.github.com>
Co-authored-by: dni  <office@dnilabs.com>
This commit is contained in:
Vlad Stan 2025-07-17 17:11:40 +03:00 committed by GitHub
parent a36ab2d408
commit dce2bfb440
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 306 additions and 207 deletions

View file

@ -27,7 +27,7 @@ from lnbits.core.crud.extensions import create_installed_extension
from lnbits.core.helpers import migrate_extension_database
from lnbits.core.models.notifications import NotificationType
from lnbits.core.services.extensions import deactivate_extension, get_valid_extensions
from lnbits.core.services.notifications import enqueue_notification
from lnbits.core.services.notifications import enqueue_admin_notification
from lnbits.core.services.payments import check_pending_payments
from lnbits.core.tasks import (
audit_queue,
@ -104,7 +104,7 @@ async def startup(app: FastAPI):
# initialize tasks
register_async_tasks()
enqueue_notification(
enqueue_admin_notification(
NotificationType.server_start_stop,
{
"message": "LNbits server started.",
@ -118,7 +118,7 @@ async def startup(app: FastAPI):
async def shutdown():
logger.warning("LNbits shutting down...")
enqueue_notification(
enqueue_admin_notification(
NotificationType.server_start_stop,
{
"message": "LNbits server shutting down...",

View file

@ -2,6 +2,8 @@ from enum import Enum
from pydantic import BaseModel
from lnbits.core.models.users import UserNotifications
class NotificationType(Enum):
server_status = "server_status"
@ -18,6 +20,7 @@ class NotificationType(Enum):
class NotificationMessage(BaseModel):
message_type: NotificationType
values: dict
user_notifications: UserNotifications | None = None
NOTIFICATION_TEMPLATES = {

View file

@ -20,14 +20,20 @@ from lnbits.settings import settings
from .wallets import Wallet
class UserNotifications(BaseModel):
nostr_identifier: str | None = None
telegram_chat_id: str | None = None
email_address: str | None = None
excluded_wallets: list[str] = []
outgoing_payments_sats: int = 0
incoming_payments_sats: int = 0
class UserExtra(BaseModel):
email_verified: bool | None = False
first_name: str | None = None
last_name: str | None = None
display_name: str | None = None
nostr_notification_identifiers: list[str] = []
telegram_chat_id: str | None = None
notifications_excluded_wallets: list[str] = []
picture: str | None = None
# Auth provider, possible values:
# - "env": the user was created automatically by the system
@ -38,6 +44,8 @@ class UserExtra(BaseModel):
# how many wallets are shown in the user interface
visible_wallet_count: int | None = 10
notifications: UserNotifications = UserNotifications()
class EndpointAccess(BaseModel):
path: str

View file

@ -3,7 +3,7 @@ from .funding_source import (
switch_to_voidwallet,
)
from .lnurl import fetch_lnurl_pay_request
from .notifications import enqueue_notification, send_payment_notification
from .notifications import enqueue_admin_notification, send_payment_notification
from .payments import (
calculate_fiat_amounts,
cancel_hold_invoice,
@ -49,7 +49,7 @@ __all__ = [
"create_user_account",
"create_user_account_no_ckeck",
"create_wallet_invoice",
"enqueue_notification",
"enqueue_admin_notification",
"fee_reserve",
"fee_reserve_total",
"fetch_lnurl_pay_request",

View file

@ -1,7 +1,7 @@
from loguru import logger
from lnbits.core.models.notifications import NotificationType
from lnbits.core.services.notifications import enqueue_notification
from lnbits.core.services.notifications import enqueue_admin_notification
from lnbits.settings import settings
from lnbits.wallets import get_funding_source, set_funding_source
@ -51,7 +51,7 @@ async def check_server_balance_against_node():
f"Balance delta reached: {status.delta_sats} sats."
f" Switch to void wallet: {use_voidwallet}."
)
enqueue_notification(
enqueue_admin_notification(
NotificationType.watchdog_check,
{
"delta_sats": status.delta_sats,
@ -71,7 +71,7 @@ async def check_balance_delta_changed():
settings.latest_balance_delta_sats = status.delta_sats
return
if status.delta_sats != settings.latest_balance_delta_sats:
enqueue_notification(
enqueue_admin_notification(
NotificationType.balance_delta,
{
"delta_sats": status.delta_sats,

View file

@ -16,12 +16,14 @@ from lnbits.core.crud import (
get_webpush_subscriptions_for_user,
mark_webhook_sent,
)
from lnbits.core.crud.users import get_user
from lnbits.core.models import Payment, Wallet
from lnbits.core.models.notifications import (
NOTIFICATION_TEMPLATES,
NotificationMessage,
NotificationType,
)
from lnbits.core.models.users import UserNotifications
from lnbits.core.services.nostr import fetch_nip5_details, send_nostr_dm
from lnbits.core.services.websockets import websocket_manager
from lnbits.helpers import check_callback_url, is_valid_email_address
@ -31,8 +33,8 @@ from lnbits.utils.nostr import normalize_private_key
notifications_queue: asyncio.Queue[NotificationMessage] = asyncio.Queue()
def enqueue_notification(message_type: NotificationType, values: dict) -> None:
if not is_message_type_enabled(message_type):
def enqueue_admin_notification(message_type: NotificationType, values: dict) -> None:
if not _is_message_type_enabled(message_type):
return
try:
notifications_queue.put_nowait(
@ -42,62 +44,125 @@ def enqueue_notification(message_type: NotificationType, values: dict) -> None:
logger.error(f"Error enqueuing notification: {e}")
def enqueue_user_notification(
message_type: NotificationType, values: dict, user_notifications: UserNotifications
) -> None:
try:
notifications_queue.put_nowait(
NotificationMessage(
message_type=message_type,
values=values,
user_notifications=user_notifications,
)
)
except Exception as e:
logger.error(f"Error enqueuing notification: {e}")
async def process_next_notification() -> None:
notification_message = await notifications_queue.get()
message_type, text = _notification_message_to_text(notification_message)
await send_notification(text, message_type)
user_notifications = notification_message.user_notifications
if user_notifications:
await send_user_notification(user_notifications, text, message_type)
else:
await send_admin_notification(text, message_type)
async def send_admin_notification(
message: str,
message_type: Optional[str] = None,
) -> None:
return await send_notification(
settings.lnbits_telegram_notifications_chat_id,
settings.lnbits_nostr_notifications_identifiers,
settings.lnbits_email_notifications_to_emails,
message,
message_type,
)
async def send_user_notification(
user_notifications: UserNotifications,
message: str,
message_type: Optional[str] = None,
) -> None:
email_address = (
[user_notifications.email_address] if user_notifications.email_address else []
)
nostr_identifiers = (
[user_notifications.nostr_identifier]
if user_notifications.nostr_identifier
else []
)
return await send_notification(
user_notifications.telegram_chat_id,
nostr_identifiers,
email_address,
message,
message_type,
)
async def send_notification(
telegram_chat_id: str | None,
nostr_identifiers: list[str] | None,
email_addresses: list[str] | None,
message: str,
message_type: Optional[str] = None,
) -> None:
try:
if settings.lnbits_telegram_notifications_enabled:
await send_telegram_notification(message)
if telegram_chat_id and settings.is_telegram_notifications_configured():
await send_telegram_notification(telegram_chat_id, message)
logger.debug(f"Sent telegram notification: {message_type}")
except Exception as e:
logger.error(f"Error sending telegram notification {message_type}: {e}")
try:
if settings.lnbits_nostr_notifications_enabled:
await send_nostr_notification(message)
if nostr_identifiers and settings.is_nostr_notifications_configured():
await send_nostr_notifications(nostr_identifiers, message)
logger.debug(f"Sent nostr notification: {message_type}")
except Exception as e:
logger.error(f"Error sending nostr notification {message_type}: {e}")
try:
if settings.lnbits_email_notifications_enabled:
await send_email_notification(message)
if email_addresses and settings.lnbits_email_notifications_enabled:
await send_email_notification(email_addresses, message)
logger.debug(f"Sent email notification: {message_type}")
except Exception as e:
logger.error(f"Error sending email notification {message_type}: {e}")
async def send_nostr_notification(message: str) -> dict:
for i in settings.lnbits_nostr_notifications_identifiers:
async def send_nostr_notifications(identifiers: list[str], message: str) -> list[str]:
success_sent: list[str] = []
for identifier in identifiers:
try:
identifier = await fetch_nip5_details(i)
user_pubkey = identifier[0]
relays = identifier[1]
server_private_key = normalize_private_key(
settings.lnbits_nostr_notifications_private_key
)
await send_nostr_dm(
server_private_key,
user_pubkey,
message,
relays,
)
await send_nostr_notification(identifier, message)
success_sent.append(identifier)
except Exception as e:
logger.warning(f"Error notifying identifier {i}: {e}")
return {"status": "ok"}
logger.warning(f"Error notifying identifier {identifier}: {e}")
return success_sent
async def send_telegram_notification(message: str) -> dict:
async def send_nostr_notification(identifier: str, message: str):
nip5 = await fetch_nip5_details(identifier)
user_pubkey = nip5[0]
relays = nip5[1]
server_private_key = normalize_private_key(
settings.lnbits_nostr_notifications_private_key
)
await send_nostr_dm(
server_private_key,
user_pubkey,
message,
relays,
)
async def send_telegram_notification(chat_id: str, message: str) -> dict:
return await send_telegram_message(
settings.lnbits_telegram_notifications_access_token,
settings.lnbits_telegram_notifications_chat_id,
chat_id,
message,
)
@ -112,7 +177,7 @@ async def send_telegram_message(token: str, chat_id: str, message: str) -> dict:
async def send_email_notification(
message: str, subject: str = "LNbits Notification"
to_emails: list[str], message: str, subject: str = "LNbits Notification"
) -> dict:
if not settings.lnbits_email_notifications_enabled:
return {"status": "error", "message": "Email notifications are disabled"}
@ -123,7 +188,7 @@ async def send_email_notification(
settings.lnbits_email_notifications_username,
settings.lnbits_email_notifications_password,
settings.lnbits_email_notifications_email,
settings.lnbits_email_notifications_to_emails,
to_emails,
subject,
message,
)
@ -163,41 +228,6 @@ async def send_email(
return True
def is_message_type_enabled(message_type: NotificationType) -> bool:
if message_type == NotificationType.balance_update:
return settings.lnbits_notification_credit_debit
if message_type == NotificationType.settings_update:
return settings.lnbits_notification_settings_update
if message_type == NotificationType.watchdog_check:
return settings.lnbits_notification_watchdog
if message_type == NotificationType.balance_delta:
return settings.notification_balance_delta_changed
if message_type == NotificationType.server_start_stop:
return settings.lnbits_notification_server_start_stop
if message_type == NotificationType.server_status:
return settings.lnbits_notification_server_status_hours > 0
if message_type == NotificationType.incoming_payment:
return settings.lnbits_notification_incoming_payment_amount_sats > 0
if message_type == NotificationType.outgoing_payment:
return settings.lnbits_notification_outgoing_payment_amount_sats > 0
return False
def _notification_message_to_text(
notification_message: NotificationMessage,
) -> tuple[str, str]:
message_type = notification_message.message_type.value
meesage_value = NOTIFICATION_TEMPLATES.get(message_type, message_type)
try:
text = meesage_value.format(**notification_message.values)
except Exception as e:
logger.warning(f"Error formatting notification message: {e}")
text = meesage_value
text = f"""[{settings.lnbits_site_title}]\n{text}"""
return message_type, text
async def dispatch_webhook(payment: Payment):
"""
Dispatches the webhook to the webhook url.
@ -231,7 +261,7 @@ async def send_payment_notification(wallet: Wallet, payment: Payment):
except Exception as e:
logger.error(f"Error sending websocket payment notification {e!s}")
try:
send_chat_payment_notification(wallet, payment)
await send_chat_payment_notification(wallet, payment)
except Exception as e:
logger.error(f"Error sending chat payment notification {e!s}")
try:
@ -268,7 +298,7 @@ async def send_ws_payment_notification(wallet: Wallet, payment: Payment):
)
def send_chat_payment_notification(wallet: Wallet, payment: Payment):
async def send_chat_payment_notification(wallet: Wallet, payment: Payment):
amount_sats = abs(payment.sat)
values: dict = {
"wallet_id": wallet.id,
@ -283,10 +313,23 @@ def send_chat_payment_notification(wallet: Wallet, payment: Payment):
if payment.is_out:
if amount_sats >= settings.lnbits_notification_outgoing_payment_amount_sats:
enqueue_notification(NotificationType.outgoing_payment, values)
else:
if amount_sats >= settings.lnbits_notification_incoming_payment_amount_sats:
enqueue_notification(NotificationType.incoming_payment, values)
enqueue_admin_notification(NotificationType.outgoing_payment, values)
elif amount_sats >= settings.lnbits_notification_incoming_payment_amount_sats:
enqueue_admin_notification(NotificationType.incoming_payment, values)
user = await get_user(wallet.user)
user_notifications = user.extra.notifications if user else None
if user_notifications and wallet.id not in user_notifications.excluded_wallets:
out_limit = user_notifications.outgoing_payments_sats
in_limit = user_notifications.incoming_payments_sats
if payment.is_out and (amount_sats >= out_limit):
enqueue_user_notification(
NotificationType.outgoing_payment, values, user_notifications
)
elif amount_sats >= in_limit:
enqueue_user_notification(
NotificationType.incoming_payment, values, user_notifications
)
async def send_payment_push_notification(wallet: Wallet, payment: Payment):
@ -330,3 +373,38 @@ async def send_push_notification(subscription, title, body, url=""):
f"failed sending push notification: "
f"{e.response.text if e.response else e}"
)
def _is_message_type_enabled(message_type: NotificationType) -> bool:
if message_type == NotificationType.balance_update:
return settings.lnbits_notification_credit_debit
if message_type == NotificationType.settings_update:
return settings.lnbits_notification_settings_update
if message_type == NotificationType.watchdog_check:
return settings.lnbits_notification_watchdog
if message_type == NotificationType.balance_delta:
return settings.notification_balance_delta_changed
if message_type == NotificationType.server_start_stop:
return settings.lnbits_notification_server_start_stop
if message_type == NotificationType.server_status:
return settings.lnbits_notification_server_status_hours > 0
if message_type == NotificationType.incoming_payment:
return settings.lnbits_notification_incoming_payment_amount_sats > 0
if message_type == NotificationType.outgoing_payment:
return settings.lnbits_notification_outgoing_payment_amount_sats > 0
return False
def _notification_message_to_text(
notification_message: NotificationMessage,
) -> tuple[str, str]:
message_type = notification_message.message_type.value
meesage_value = NOTIFICATION_TEMPLATES.get(message_type, message_type)
try:
text = meesage_value.format(**notification_message.values)
except Exception as e:
logger.warning(f"Error formatting notification message: {e}")
text = meesage_value
text = f"""[{settings.lnbits_site_title}]\n{text}"""
return message_type, text

View file

@ -91,12 +91,8 @@ async def pay_invoice(
payment = await _pay_invoice(wallet.id, create_payment_model, conn)
service_fee_memo = f"""
Service fee for payment of {abs(payment.sat)} sats.
Wallet: '{wallet.name}' ({wallet.id})."""
async with db.reuse_conn(conn) if conn else db.connect() as new_conn:
await _credit_service_fee_wallet(payment, service_fee_memo, new_conn)
await _credit_service_fee_wallet(wallet, payment, new_conn)
return payment
@ -905,12 +901,16 @@ def _validate_payment_request(
async def _credit_service_fee_wallet(
payment: Payment, memo: str, conn: Optional[Connection] = None
wallet: Wallet, payment: Payment, conn: Optional[Connection] = None
):
service_fee_msat = service_fee(payment.amount, internal=payment.is_internal)
if not settings.lnbits_service_fee_wallet or not service_fee_msat:
return
memo = f"""
Service fee for payment of {abs(payment.sat)} sats.
Wallet: '{wallet.name}' ({wallet.id})."""
create_payment_model = CreatePayment(
wallet_id=settings.lnbits_service_fee_wallet,
bolt11=payment.bolt11,

View file

@ -22,7 +22,7 @@ from lnbits.core.services.funding_source import (
get_balance_delta,
)
from lnbits.core.services.notifications import (
enqueue_notification,
enqueue_admin_notification,
process_next_notification,
send_payment_notification,
)
@ -87,7 +87,7 @@ async def _notify_server_status() -> None:
"lnbits_balance_sats": status.lnbits_balance_sats,
"node_balance_sats": status.node_balance_sats,
}
enqueue_notification(NotificationType.server_status, values)
enqueue_admin_notification(NotificationType.server_status, values)
async def wait_for_paid_invoices(invoice_paid_queue: asyncio.Queue) -> None:
@ -123,7 +123,7 @@ async def wait_notification_messages() -> None:
try:
await process_next_notification()
except Exception as ex:
logger.log("error", ex)
logger.warning("Payment notification error", ex)
await asyncio.sleep(3)

View file

@ -7,43 +7,71 @@
<div class="row q-col-gutter-md q-mb-md">
<div class="col-12">
<q-card>
<div>
<div class="q-gutter-y-md">
<q-tabs v-model="tab" align="left">
<q-tab
name="user"
:label="$t('account_settings')"
@update="val => tab = val.name"
></q-tab>
<q-tab
name="theme"
:label="$t('look_and_feel')"
@update="val => tab = val.name"
></q-tab>
<q-tab
name="notifications"
:label="$t('notifications')"
@update="val => tab = val.name"
></q-tab>
<q-tab
name="api_acls"
:label="$t('access_control_list')"
@update="val => tab = val.name"
></q-tab>
</q-tabs>
<div class="q-pa-sm">
<div class="row items-center justify-between q-gutter-xs">
<div class="col">
<q-btn @click="updateAccount" unelevated color="primary">
<span v-text="$t('update_account')"></span>
</q-btn>
</div>
</div>
</div>
</q-card>
</div>
</div>
<div class="row q-col-gutter-md">
<div v-if="user" class="col-md-12 col-lg-6 q-gutter-y-md">
<div class="row q-col-gutter-md q-mb-md">
<div class="col-12">
<q-card>
<q-card-section>
<div class="row">
<div class="col">
<q-tab-panels v-model="tab">
<q-splitter>
<template v-slot:before>
<q-tabs v-model="tab" vertical active-color="primary">
<q-tab
name="user"
icon="person"
:label="$q.screen.gt.sm ? $t('account_settings') : ''"
@update="val => tab = val.name"
>
<q-tooltip v-if="!$q.screen.gt.sm"
><span v-text="$t('account_settings')"></span
></q-tooltip>
</q-tab>
<q-tab
name="notifications"
icon="notifications"
:label="$q.screen.gt.sm ? $t('notifications') : ''"
@update="val => tab = val.name"
>
<q-tooltip v-if="!$q.screen.gt.sm"
><span v-text="$t('notifications')"></span
></q-tooltip>
</q-tab>
<q-tab
name="theme"
icon="palette"
:label="$q.screen.gt.sm ? $t('look_and_feel') : ''"
@update="val => tab = val.name"
>
<q-tooltip v-if="!$q.screen.gt.sm"
><span v-text="$t('look_and_feel')"></span
></q-tooltip>
</q-tab>
<q-tab
name="api_acls"
icon="lock"
:label="$q.screen.gt.sm ? $t('access_control_list') : ''"
@update="val => tab = val.name"
>
<q-tooltip v-if="!$q.screen.gt.sm"
><span v-text="$t('access_control_list')"></span
></q-tooltip>
</q-tab>
</q-tabs>
</template>
<template v-slot:after>
<q-scroll-area style="height: 80vh">
<q-tab-panels v-if="user" v-model="tab">
<q-tab-panel name="user">
<div v-if="credentialsData.show">
<q-card-section>
@ -298,9 +326,6 @@
</q-card-section>
<q-separator></q-separator>
<q-card-section>
<q-btn @click="updateAccount" unelevated color="primary">
<span v-text="$t('update_account')"></span>
</q-btn>
<q-btn
@click="showUpdateCredentials()"
:label="$t('change_password')"
@ -564,63 +589,80 @@
</q-select>
</div>
</div>
<q-separator></q-separator>
<div class="row q-mb-md q-mt-md">
<q-btn @click="updateAccount" unelevated color="primary">
<span v-text="$t('update_account')"></span>
</q-btn>
</div>
</q-tab-panel>
<q-tab-panel name="notifications">
<q-card-section>
<div class="row q-mb-md">
<div class="col-4">
<span
v-text="$t('notifications_nostr_identifiers')"
v-text="$t('notifications_nostr_identifier')"
></span>
{%if not nostr_configured%}
<br />
<q-badge v-text="$t('not_connected')"></q-badge>
{%endif%}
</div>
<div class="col-8">
<q-input
filled
dense
v-model="notifications.nostr.identifier"
:hint="$t('notifications_nostr_identifiers_desc')"
@keydown.enter="addNostrNotificationIdentifier"
v-model="user.extra.notifications.nostr_identifier"
:hint="$t('notifications_nostr_identifier_desc')"
>
<q-btn
@click="addNostrNotificationIdentifier()"
dense
flat
icon="add"
></q-btn>
</q-input>
<div>
<q-chip
v-for="identifier in user.extra.nostr_notification_identifiers"
:key="identifier"
removable
@remove="removeNostrNotificationIdentifier(identifier)"
color="primary"
text-color="white"
><span class="ellipsis" v-text="identifier"></span
></q-chip>
</div>
</div>
</div>
<div class="row q-mb-md">
<div class="col-4">
<span v-text="$t('notifications_chat_id')"></span>
{%if not telegram_configured%}
<br />
<q-badge v-text="$t('not_connected')"></q-badge>
{%endif%}
</div>
<div class="col-8">
<q-input
filled
dense
v-model="user.extra.telegram_chat_id"
v-model="user.extra.notifications.telegram_chat_id"
:hint="$t('notifications_chat_id_desc')"
/>
</div>
</div>
<q-separator class="q-mb-md"></q-separator>
<div class="row q-mb-md">
<div class="col-4">
<span v-text="$t('notification_outgoing_payment')"></span>
</div>
<div class="col-8">
<q-input
filled
dense
type="number"
min="0"
step="1"
v-model="user.extra.notifications.outgoing_payments_sats"
:hint="$t('notification_outgoing_payment_desc')"
/>
</div>
</div>
<div class="row q-mb-md">
<div class="col-4">
<span v-text="$t('notification_incoming_payment')"></span>
</div>
<div class="col-8">
<q-input
filled
dense
type="number"
min="0"
step="1"
v-model="user.extra.notifications.incoming_payments_sats"
:hint="$t('notification_incoming_payment_desc')"
/>
</div>
</div>
<div class="row q-mb-md">
<div class="col-4">
<span v-text="$t('exclude_wallets')"></span>
@ -632,7 +674,7 @@
emit-value
map-options
multiple
v-model="user.extra.notifications_excluded_wallets"
v-model="user.extra.notifications.excluded_wallets"
:options="g.user.walletOptions"
:label="$t('exclude_wallets')"
:hint="$t('notifications_excluded_wallets_desc')"
@ -642,14 +684,6 @@
</div>
</div>
</q-card-section>
<q-card-section>
<q-separator></q-separator>
<div class="row q-mb-md q-mt-md">
<q-btn @click="updateAccount" unelevated color="primary">
<span v-text="$t('update_account')"></span>
</q-btn>
</div>
</q-card-section>
</q-tab-panel>
<q-tab-panel name="api_acls">
<div class="row q-mb-md">
@ -876,16 +910,9 @@
</q-card-section>
</q-tab-panel>
</q-tab-panels>
</div>
</div>
</q-card-section>
</q-card>
</div>
<div v-else class="col-12 col-md-6 q-gutter-y-md">
<q-card>
<q-card-section>
<h4 class="q-my-none"><span v-text="$t('account')"></span></h4>
</q-card-section>
</q-scroll-area>
</template>
</q-splitter>
</q-card>
</div>
</div>

View file

@ -16,7 +16,7 @@ from lnbits.core.models import User
from lnbits.core.models.misc import Image, SimpleStatus
from lnbits.core.models.notifications import NotificationType
from lnbits.core.services import (
enqueue_notification,
enqueue_admin_notification,
get_balance_delta,
update_cached_settings,
)
@ -65,7 +65,9 @@ async def api_monitor():
)
async def api_test_email():
return await send_email_notification(
"This is a LNbits test email.", "LNbits Test Email"
settings.lnbits_email_notifications_to_emails,
"This is a LNbits test email.",
"LNbits Test Email",
)
@ -82,7 +84,9 @@ async def api_get_settings(
status_code=HTTPStatus.OK,
)
async def api_update_settings(data: UpdateSettings, user: User = Depends(check_admin)):
enqueue_notification(NotificationType.settings_update, {"username": user.username})
enqueue_admin_notification(
NotificationType.settings_update, {"username": user.username}
)
await update_admin_settings(data)
admin_settings = await get_admin_settings(user.super_user)
if not admin_settings:
@ -113,7 +117,9 @@ async def api_reset_settings(field_name: str):
@admin_router.delete("/api/v1/settings", status_code=HTTPStatus.OK)
async def api_delete_settings(user: User = Depends(check_super_user)) -> None:
enqueue_notification(NotificationType.settings_update, {"username": user.username})
enqueue_admin_notification(
NotificationType.settings_update, {"username": user.username}
)
await reset_core_settings()
server_restart.set()

View file

@ -345,7 +345,6 @@ async def node(request: Request, user: User = Depends(check_admin)):
"node/index.html",
{
"user": user.json(),
"settings": settings.dict(),
"balance": balance,
"wallets": user.wallets[0].json(),
"ajax": _is_ajax_request(request),
@ -383,7 +382,6 @@ async def admin_index(request: Request, user: User = Depends(check_admin)):
"admin/index.html",
{
"user": user.json(),
"settings": settings.dict(),
"balance": balance,
"currencies": list(currencies.keys()),
"ajax": _is_ajax_request(request),

View file

@ -35,7 +35,7 @@ from lnbits.core.models.notifications import NotificationType
from lnbits.core.models.users import Account
from lnbits.core.services import (
create_user_account_no_ckeck,
enqueue_notification,
enqueue_admin_notification,
update_user_account,
update_user_extensions,
update_wallet_balance,
@ -321,7 +321,7 @@ async def api_update_balance(data: UpdateBalance) -> SimpleStatus:
if not wallet:
raise HTTPException(HTTPStatus.NOT_FOUND, "Wallet not found.")
await update_wallet_balance(wallet=wallet, amount=int(data.amount))
enqueue_notification(
enqueue_admin_notification(
NotificationType.balance_update,
{
"amount": int(data.amount),

File diff suppressed because one or more lines are too long

View file

@ -230,6 +230,9 @@ window.localisation.en = {
notifications_nostr_private_key: 'Nostr Private Key',
notifications_nostr_private_key_desc:
'Private key (hex or nsec) to sign the messages sent to Nostr',
notifications_nostr_identifier: 'Nostr Identifier',
notifications_nostr_identifier_desc:
'Nip5 identifier to send notifications to',
notifications_nostr_identifiers: 'Nostr Identifiers',
notifications_nostr_identifiers_desc:
'List of identifiers to send notifications to',
@ -646,5 +649,7 @@ window.localisation.en = {
'Signing secret for the webhook. Messages will be signed with this secret.',
callback_success_url: 'Callback Success URL',
callback_success_url_hint:
'The user will be redirected to this URL after the payment is successful'
'The user will be redirected to this URL after the payment is successful',
connected: 'Connected',
not_connected: 'Not Connected'
}

View file

@ -399,32 +399,6 @@ window.AccountPageLogic = {
} finally {
this.apiAcl.password = ''
}
},
addNostrNotificationIdentifier() {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!emailRegex.test(this.notifications.nostr.identifier)) {
Quasar.Notify.create({
type: 'warning',
message: 'Invalid email format.'
})
return
}
const identifier = this.user.extra.nostr_notification_identifiers.find(
i => i === this.notifications.nostr.identifier
)
if (identifier) {
return
}
this.user.extra.nostr_notification_identifiers.push(
this.notifications.nostr.identifier
)
this.notifications.nostr.identifier = ''
},
removeNostrNotificationIdentifier(identifier) {
this.user.extra.nostr_notification_identifiers =
this.user.extra.nostr_notification_identifiers.filter(
i => i !== identifier
)
}
},