292 lines
8.5 KiB
Python
292 lines
8.5 KiB
Python
from datetime import datetime, timezone
|
|
from time import time
|
|
from typing import Any
|
|
from uuid import uuid4
|
|
|
|
from lnbits.core.crud.extensions import get_user_active_extensions_ids
|
|
from lnbits.core.crud.wallets import (
|
|
clear_wallet_cache,
|
|
create_wallet,
|
|
get_standalone_wallet,
|
|
get_wallets,
|
|
)
|
|
from lnbits.core.db import db
|
|
from lnbits.core.models import UserAcls
|
|
from lnbits.db import Connection, Filters, Page
|
|
from lnbits.helpers import sha256s
|
|
from lnbits.utils.cache import cache
|
|
|
|
from ..models import (
|
|
Account,
|
|
AccountFilters,
|
|
AccountOverview,
|
|
User,
|
|
)
|
|
|
|
|
|
async def create_account(
|
|
account: Account | None = None,
|
|
conn: Connection | None = None,
|
|
) -> Account:
|
|
if account:
|
|
account.validate_fields()
|
|
else:
|
|
now = datetime.now(timezone.utc)
|
|
account = Account(id=uuid4().hex, created_at=now, updated_at=now)
|
|
await (conn or db).insert("accounts", account)
|
|
return account
|
|
|
|
|
|
async def update_account(account: Account, conn: Connection | None = None) -> Account:
|
|
account.updated_at = datetime.now(timezone.utc)
|
|
await (conn or db).update("accounts", account)
|
|
return account
|
|
|
|
|
|
async def delete_account(user_id: str, conn: Connection | None = None) -> None:
|
|
await (conn or db).execute(
|
|
"DELETE from accounts WHERE id = :user",
|
|
{"user": user_id},
|
|
)
|
|
await clear_user_id_cache(user_id)
|
|
|
|
|
|
async def get_accounts(
|
|
filters: Filters[AccountFilters] | None = None,
|
|
conn: Connection | None = None,
|
|
) -> Page[AccountOverview]:
|
|
where_clauses = []
|
|
values: dict[str, Any] = {}
|
|
filters = filters or Filters()
|
|
|
|
wallet_filter = filters.get_filter_by_field("wallet_id")
|
|
|
|
if wallet_filter and wallet_filter.values:
|
|
wallet_id_value = next(iter(wallet_filter.values.values()), None)
|
|
wallet = (
|
|
await get_standalone_wallet(wallet_id_value, deleted=None, conn=conn)
|
|
if wallet_id_value
|
|
else None
|
|
)
|
|
if not wallet:
|
|
return Page(data=[], total=0)
|
|
where_clauses.append("accounts.id = :account_id")
|
|
values = {**values, "account_id": wallet.user}
|
|
filters.remove_filter_by_field("wallet_id")
|
|
|
|
return await (conn or db).fetch_page(
|
|
"""
|
|
SELECT
|
|
accounts.id,
|
|
accounts.username,
|
|
accounts.email,
|
|
accounts.pubkey,
|
|
accounts.external_id,
|
|
accounts.activated,
|
|
SUM(COALESCE((
|
|
SELECT balance FROM balances WHERE wallet_id = wallets.id
|
|
), 0)) as balance_msat,
|
|
SUM((
|
|
SELECT COUNT(*) FROM apipayments WHERE wallet_id = wallets.id
|
|
)) as transaction_count,
|
|
(
|
|
SELECT COUNT(*) FROM wallets WHERE wallets.user = accounts.id
|
|
) as wallet_count,
|
|
MAX((
|
|
SELECT time FROM apipayments
|
|
WHERE wallet_id = wallets.id ORDER BY time DESC LIMIT 1
|
|
)) as last_payment
|
|
FROM accounts LEFT JOIN wallets ON accounts.id = wallets.user
|
|
""",
|
|
where_clauses,
|
|
values,
|
|
filters=filters,
|
|
model=AccountOverview,
|
|
group_by=["accounts.id"],
|
|
table_name="accounts",
|
|
)
|
|
|
|
|
|
async def get_account(
|
|
user_id: str, active_only: bool = True, conn: Connection | None = None
|
|
) -> Account | None:
|
|
if len(user_id) == 0:
|
|
return None
|
|
|
|
return await (conn or db).fetchone(
|
|
"""
|
|
SELECT * FROM accounts
|
|
WHERE id = :id AND (activated = true OR activated = :activated)
|
|
""",
|
|
{"id": user_id, "activated": active_only},
|
|
Account,
|
|
)
|
|
|
|
|
|
async def delete_accounts_no_wallets(
|
|
time_delta: int,
|
|
conn: Connection | None = None,
|
|
) -> None:
|
|
delta = int(time()) - time_delta
|
|
await (conn or db).execute(
|
|
# Timestamp placeholder is safe from SQL injection (not user input)
|
|
f"""
|
|
DELETE FROM accounts
|
|
WHERE NOT EXISTS (
|
|
SELECT wallets.id FROM wallets WHERE wallets.user = accounts.id
|
|
) AND (
|
|
(updated_at is null AND created_at < :delta)
|
|
OR updated_at < {db.timestamp_placeholder("delta")}
|
|
)
|
|
""", # noqa: S608
|
|
{"delta": delta},
|
|
)
|
|
|
|
|
|
async def get_account_by_username(
|
|
username: str, active_only: bool = True, conn: Connection | None = None
|
|
) -> Account | None:
|
|
if len(username) == 0:
|
|
return None
|
|
|
|
return await (conn or db).fetchone(
|
|
"""
|
|
SELECT * FROM accounts
|
|
WHERE
|
|
LOWER(username) = :username
|
|
AND (activated = true OR activated = :activated)
|
|
""",
|
|
{"username": username.lower(), "activated": active_only},
|
|
Account,
|
|
)
|
|
|
|
|
|
async def get_account_by_pubkey(
|
|
pubkey: str, active_only: bool = True, conn: Connection | None = None
|
|
) -> Account | None:
|
|
return await (conn or db).fetchone(
|
|
"""
|
|
SELECT * FROM accounts
|
|
WHERE
|
|
LOWER(pubkey) = :pubkey
|
|
AND (activated = true OR activated = :activated)
|
|
""",
|
|
{"pubkey": pubkey.lower(), "activated": active_only},
|
|
Account,
|
|
)
|
|
|
|
|
|
async def get_account_by_email(
|
|
email: str, active_only: bool = True, conn: Connection | None = None
|
|
) -> Account | None:
|
|
if len(email) == 0:
|
|
return None
|
|
|
|
return await (conn or db).fetchone(
|
|
"""
|
|
SELECT * FROM accounts
|
|
WHERE
|
|
LOWER(email) = :email
|
|
AND (activated = true OR activated = :activated)
|
|
""",
|
|
{"email": email.lower(), "activated": active_only},
|
|
Account,
|
|
)
|
|
|
|
|
|
async def get_account_by_username_or_email(
|
|
username_or_email: str,
|
|
active_only: bool = True,
|
|
conn: Connection | None = None,
|
|
) -> Account | None:
|
|
|
|
return await (conn or db).fetchone(
|
|
"""
|
|
SELECT * FROM accounts
|
|
WHERE
|
|
(LOWER(email) = :value or LOWER(username) = :value)
|
|
AND (activated = true OR activated = :activated)
|
|
""",
|
|
{"value": username_or_email.lower(), "activated": active_only},
|
|
Account,
|
|
)
|
|
|
|
|
|
async def get_user(
|
|
user_id: str, active_only: bool = True, conn: Connection | None = None
|
|
) -> User | None:
|
|
async with db.reuse_conn(conn) if conn else db.connect() as conn:
|
|
account = await get_account(user_id, active_only, conn=conn)
|
|
if not account:
|
|
return None
|
|
return await get_user_from_account(account, conn=conn)
|
|
|
|
|
|
async def get_user_from_account(
|
|
account: Account, conn: Connection | None = None
|
|
) -> User | None:
|
|
async with db.reuse_conn(conn) if conn else db.connect() as conn:
|
|
extensions = await get_user_active_extensions_ids(account.id, conn=conn)
|
|
wallets = await get_wallets(account.id, deleted=False, conn=conn)
|
|
|
|
if len(wallets) == 0:
|
|
wallet = await create_wallet(user_id=account.id, conn=conn)
|
|
wallets.append(wallet)
|
|
|
|
return User(
|
|
id=account.id,
|
|
activated=account.activated,
|
|
email=account.email,
|
|
username=account.username,
|
|
pubkey=account.pubkey,
|
|
external_id=account.external_id,
|
|
extra=account.extra,
|
|
created_at=account.created_at,
|
|
updated_at=account.updated_at,
|
|
extensions=extensions,
|
|
wallets=wallets,
|
|
admin=account.is_admin,
|
|
super_user=account.is_super_user,
|
|
fiat_providers=account.fiat_providers,
|
|
has_password=account.password_hash is not None,
|
|
ui_customization=account.ui_customization or {},
|
|
)
|
|
|
|
|
|
async def update_user_access_control_list(
|
|
user_acls: UserAcls, conn: Connection | None = None
|
|
):
|
|
user_acls.updated_at = datetime.now(timezone.utc)
|
|
await (conn or db).update("accounts", user_acls)
|
|
|
|
|
|
async def get_user_access_control_lists(
|
|
user_id: str, active_only: bool = True, conn: Connection | None = None
|
|
) -> UserAcls:
|
|
user_acls = await (conn or db).fetchone(
|
|
"""
|
|
SELECT id, access_control_list FROM accounts
|
|
WHERE id = :user_id AND (activated = true OR activated = :activated)
|
|
""",
|
|
{"user_id": user_id, "activated": active_only},
|
|
UserAcls,
|
|
)
|
|
|
|
return user_acls or UserAcls(id=user_id)
|
|
|
|
|
|
async def clear_user_id_cache(user_id: str):
|
|
user = await get_user(user_id, active_only=True)
|
|
if user:
|
|
clear_user_cache(user)
|
|
|
|
|
|
def clear_user_cache(user: User):
|
|
user_cache_key: str | None = cache.pop(
|
|
f"auth:user:cache_key:{sha256s(user.id)}", None
|
|
)
|
|
if user_cache_key:
|
|
cache.pop(user_cache_key)
|
|
for wallet in user.wallets:
|
|
clear_wallet_cache(wallet)
|