Updates journal entry retrieval to filter entries based on the user's accounts rather than the user ID. This ensures that users only see journal entries that directly affect their accounts. Also displays fiat amount in journal entries if available in the metadata.
731 lines
24 KiB
Python
731 lines
24 KiB
Python
from http import HTTPStatus
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from lnbits.core.models import User, WalletTypeInfo
|
|
from lnbits.decorators import (
|
|
check_super_user,
|
|
check_user_exists,
|
|
require_admin_key,
|
|
require_invoice_key,
|
|
)
|
|
from lnbits.utils.exchange_rates import allowed_currencies, fiat_amount_as_satoshis
|
|
|
|
from .crud import (
|
|
create_account,
|
|
create_journal_entry,
|
|
get_account,
|
|
get_account_balance,
|
|
get_account_by_name,
|
|
get_account_transactions,
|
|
get_all_accounts,
|
|
get_all_journal_entries,
|
|
get_all_user_balances,
|
|
get_all_user_wallet_settings,
|
|
get_journal_entries_by_user,
|
|
get_journal_entry,
|
|
get_or_create_user_account,
|
|
get_user_balance,
|
|
)
|
|
from .models import (
|
|
Account,
|
|
AccountType,
|
|
CastleSettings,
|
|
CreateAccount,
|
|
CreateEntryLine,
|
|
CreateJournalEntry,
|
|
ExpenseEntry,
|
|
GeneratePaymentInvoice,
|
|
JournalEntry,
|
|
ReceivableEntry,
|
|
RecordPayment,
|
|
RevenueEntry,
|
|
UserBalance,
|
|
UserWalletSettings,
|
|
)
|
|
from .services import get_settings, get_user_wallet, update_settings, update_user_wallet
|
|
|
|
castle_api_router = APIRouter()
|
|
|
|
|
|
# ===== HELPER FUNCTIONS =====
|
|
|
|
|
|
async def check_castle_wallet_configured() -> str:
|
|
"""Ensure castle wallet is configured, return wallet_id"""
|
|
settings = await get_settings("admin")
|
|
if not settings or not settings.castle_wallet_id:
|
|
raise HTTPException(
|
|
status_code=HTTPStatus.BAD_REQUEST,
|
|
detail="Castle wallet not configured. Please contact the super user to configure the Castle wallet in settings.",
|
|
)
|
|
return settings.castle_wallet_id
|
|
|
|
|
|
async def check_user_wallet_configured(user_id: str) -> str:
|
|
"""Ensure user has configured their wallet, return wallet_id"""
|
|
from lnbits.settings import settings as lnbits_settings
|
|
|
|
# If user is super user, use the castle wallet
|
|
if user_id == lnbits_settings.super_user:
|
|
castle_settings = await get_settings("admin")
|
|
if castle_settings and castle_settings.castle_wallet_id:
|
|
return castle_settings.castle_wallet_id
|
|
raise HTTPException(
|
|
status_code=HTTPStatus.BAD_REQUEST,
|
|
detail="Castle wallet not configured. Please configure the Castle wallet in settings.",
|
|
)
|
|
|
|
# For regular users, check their personal wallet
|
|
user_wallet = await get_user_wallet(user_id)
|
|
if not user_wallet or not user_wallet.user_wallet_id:
|
|
raise HTTPException(
|
|
status_code=HTTPStatus.BAD_REQUEST,
|
|
detail="You must configure your wallet in settings before using this feature.",
|
|
)
|
|
return user_wallet.user_wallet_id
|
|
|
|
|
|
# ===== UTILITY ENDPOINTS =====
|
|
|
|
|
|
@castle_api_router.get("/api/v1/currencies")
|
|
async def api_get_currencies() -> list[str]:
|
|
"""Get list of allowed currencies for fiat conversion"""
|
|
return allowed_currencies()
|
|
|
|
|
|
# ===== ACCOUNT ENDPOINTS =====
|
|
|
|
|
|
@castle_api_router.get("/api/v1/accounts")
|
|
async def api_get_accounts() -> list[Account]:
|
|
"""Get all accounts in the chart of accounts"""
|
|
return await get_all_accounts()
|
|
|
|
|
|
@castle_api_router.post("/api/v1/accounts", status_code=HTTPStatus.CREATED)
|
|
async def api_create_account(
|
|
data: CreateAccount,
|
|
wallet: WalletTypeInfo = Depends(require_admin_key),
|
|
) -> Account:
|
|
"""Create a new account (admin only)"""
|
|
return await create_account(data)
|
|
|
|
|
|
@castle_api_router.get("/api/v1/accounts/{account_id}")
|
|
async def api_get_account(account_id: str) -> Account:
|
|
"""Get a specific account"""
|
|
account = await get_account(account_id)
|
|
if not account:
|
|
raise HTTPException(
|
|
status_code=HTTPStatus.NOT_FOUND, detail="Account not found"
|
|
)
|
|
return account
|
|
|
|
|
|
@castle_api_router.get("/api/v1/accounts/{account_id}/balance")
|
|
async def api_get_account_balance(account_id: str) -> dict:
|
|
"""Get account balance"""
|
|
balance = await get_account_balance(account_id)
|
|
return {"account_id": account_id, "balance": balance}
|
|
|
|
|
|
@castle_api_router.get("/api/v1/accounts/{account_id}/transactions")
|
|
async def api_get_account_transactions(account_id: str, limit: int = 100) -> list[dict]:
|
|
"""Get all transactions for an account"""
|
|
transactions = await get_account_transactions(account_id, limit)
|
|
return [
|
|
{
|
|
"journal_entry": entry.dict(),
|
|
"entry_line": line.dict(),
|
|
}
|
|
for entry, line in transactions
|
|
]
|
|
|
|
|
|
# ===== JOURNAL ENTRY ENDPOINTS =====
|
|
|
|
|
|
@castle_api_router.get("/api/v1/entries")
|
|
async def api_get_journal_entries(limit: int = 100) -> list[JournalEntry]:
|
|
"""Get all journal entries"""
|
|
return await get_all_journal_entries(limit)
|
|
|
|
|
|
@castle_api_router.get("/api/v1/entries/user")
|
|
async def api_get_user_entries(
|
|
wallet: WalletTypeInfo = Depends(require_invoice_key),
|
|
limit: int = 100,
|
|
) -> list[JournalEntry]:
|
|
"""Get journal entries that affect the current user's accounts"""
|
|
return await get_journal_entries_by_user(wallet.wallet.user, limit)
|
|
|
|
|
|
@castle_api_router.get("/api/v1/entries/{entry_id}")
|
|
async def api_get_journal_entry(entry_id: str) -> JournalEntry:
|
|
"""Get a specific journal entry"""
|
|
entry = await get_journal_entry(entry_id)
|
|
if not entry:
|
|
raise HTTPException(
|
|
status_code=HTTPStatus.NOT_FOUND, detail="Journal entry not found"
|
|
)
|
|
return entry
|
|
|
|
|
|
@castle_api_router.post("/api/v1/entries", status_code=HTTPStatus.CREATED)
|
|
async def api_create_journal_entry(
|
|
data: CreateJournalEntry,
|
|
wallet: WalletTypeInfo = Depends(require_invoice_key),
|
|
) -> JournalEntry:
|
|
"""Create a new journal entry"""
|
|
try:
|
|
return await create_journal_entry(data, wallet.wallet.id)
|
|
except ValueError as e:
|
|
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=str(e))
|
|
|
|
|
|
# ===== SIMPLIFIED ENTRY ENDPOINTS =====
|
|
|
|
|
|
@castle_api_router.post("/api/v1/entries/expense", status_code=HTTPStatus.CREATED)
|
|
async def api_create_expense_entry(
|
|
data: ExpenseEntry,
|
|
wallet: WalletTypeInfo = Depends(require_invoice_key),
|
|
) -> JournalEntry:
|
|
"""
|
|
Create an expense entry for a user.
|
|
If is_equity=True, records as equity contribution.
|
|
If is_equity=False, records as liability (castle owes user).
|
|
|
|
If currency is provided, amount is converted from fiat to satoshis.
|
|
"""
|
|
# Check that castle wallet is configured
|
|
await check_castle_wallet_configured()
|
|
|
|
# Check that user has configured their wallet
|
|
await check_user_wallet_configured(wallet.wallet.user)
|
|
# Handle currency conversion
|
|
amount_sats = int(data.amount)
|
|
metadata = {}
|
|
|
|
if data.currency:
|
|
# Validate currency
|
|
if data.currency.upper() not in allowed_currencies():
|
|
raise HTTPException(
|
|
status_code=HTTPStatus.BAD_REQUEST,
|
|
detail=f"Currency '{data.currency}' not allowed. Use one of: {', '.join(allowed_currencies())}",
|
|
)
|
|
|
|
# Convert fiat to satoshis
|
|
amount_sats = await fiat_amount_as_satoshis(data.amount, data.currency)
|
|
|
|
# Store currency metadata
|
|
metadata = {
|
|
"fiat_currency": data.currency.upper(),
|
|
"fiat_amount": round(data.amount, ndigits=3),
|
|
"fiat_rate": amount_sats / data.amount if data.amount > 0 else 0,
|
|
"btc_rate": (data.amount / amount_sats * 100_000_000) if amount_sats > 0 else 0,
|
|
}
|
|
|
|
# Get or create expense account
|
|
expense_account = await get_account_by_name(data.expense_account)
|
|
if not expense_account:
|
|
# Try to get it by ID
|
|
expense_account = await get_account(data.expense_account)
|
|
if not expense_account:
|
|
raise HTTPException(
|
|
status_code=HTTPStatus.NOT_FOUND,
|
|
detail=f"Expense account '{data.expense_account}' not found",
|
|
)
|
|
|
|
# Get or create user-specific account
|
|
if data.is_equity:
|
|
# Equity contribution
|
|
user_account = await get_or_create_user_account(
|
|
data.user_wallet, AccountType.EQUITY, "Member Equity"
|
|
)
|
|
else:
|
|
# Liability (castle owes user)
|
|
user_account = await get_or_create_user_account(
|
|
data.user_wallet, AccountType.LIABILITY, "Accounts Payable"
|
|
)
|
|
|
|
# Create journal entry
|
|
# DR Expense, CR User Account (Liability or Equity)
|
|
description_suffix = f" ({metadata['fiat_amount']} {metadata['fiat_currency']})" if metadata else ""
|
|
entry_data = CreateJournalEntry(
|
|
description=data.description + description_suffix,
|
|
reference=data.reference,
|
|
lines=[
|
|
CreateEntryLine(
|
|
account_id=expense_account.id,
|
|
debit=amount_sats,
|
|
credit=0,
|
|
description=f"Expense paid by user {data.user_wallet[:8]}",
|
|
metadata=metadata,
|
|
),
|
|
CreateEntryLine(
|
|
account_id=user_account.id,
|
|
debit=0,
|
|
credit=amount_sats,
|
|
description=f"{'Equity contribution' if data.is_equity else 'Amount owed to user'}",
|
|
metadata=metadata,
|
|
),
|
|
],
|
|
)
|
|
|
|
return await create_journal_entry(entry_data, wallet.wallet.id)
|
|
|
|
|
|
@castle_api_router.post("/api/v1/entries/receivable", status_code=HTTPStatus.CREATED)
|
|
async def api_create_receivable_entry(
|
|
data: ReceivableEntry,
|
|
wallet: WalletTypeInfo = Depends(require_admin_key),
|
|
) -> JournalEntry:
|
|
"""
|
|
Create an accounts receivable entry (user owes castle).
|
|
Admin only to prevent abuse.
|
|
|
|
If currency is provided, amount is converted from fiat to satoshis.
|
|
"""
|
|
# Handle currency conversion
|
|
amount_sats = int(data.amount)
|
|
metadata = {}
|
|
|
|
if data.currency:
|
|
# Validate currency
|
|
if data.currency.upper() not in allowed_currencies():
|
|
raise HTTPException(
|
|
status_code=HTTPStatus.BAD_REQUEST,
|
|
detail=f"Currency '{data.currency}' not allowed. Use one of: {', '.join(allowed_currencies())}",
|
|
)
|
|
|
|
# Convert fiat to satoshis
|
|
amount_sats = await fiat_amount_as_satoshis(data.amount, data.currency)
|
|
|
|
# Store currency metadata
|
|
metadata = {
|
|
"fiat_currency": data.currency.upper(),
|
|
"fiat_amount": round(data.amount, ndigits=3),
|
|
"fiat_rate": amount_sats / data.amount if data.amount > 0 else 0,
|
|
"btc_rate": (data.amount / amount_sats * 100_000_000) if amount_sats > 0 else 0,
|
|
}
|
|
|
|
# Get or create revenue account
|
|
revenue_account = await get_account_by_name(data.revenue_account)
|
|
if not revenue_account:
|
|
revenue_account = await get_account(data.revenue_account)
|
|
if not revenue_account:
|
|
raise HTTPException(
|
|
status_code=HTTPStatus.NOT_FOUND,
|
|
detail=f"Revenue account '{data.revenue_account}' not found",
|
|
)
|
|
|
|
# Get or create user-specific receivable account
|
|
user_receivable = await get_or_create_user_account(
|
|
data.user_id, AccountType.ASSET, "Accounts Receivable"
|
|
)
|
|
|
|
# Create journal entry
|
|
# DR Accounts Receivable (User), CR Revenue
|
|
description_suffix = f" ({metadata['fiat_amount']} {metadata['fiat_currency']})" if metadata else ""
|
|
entry_data = CreateJournalEntry(
|
|
description=data.description + description_suffix,
|
|
reference=data.reference,
|
|
lines=[
|
|
CreateEntryLine(
|
|
account_id=user_receivable.id,
|
|
debit=amount_sats,
|
|
credit=0,
|
|
description=f"Amount owed by user {data.user_id[:8]}",
|
|
metadata=metadata,
|
|
),
|
|
CreateEntryLine(
|
|
account_id=revenue_account.id,
|
|
debit=0,
|
|
credit=amount_sats,
|
|
description="Revenue earned",
|
|
metadata=metadata,
|
|
),
|
|
],
|
|
)
|
|
|
|
return await create_journal_entry(entry_data, wallet.wallet.id)
|
|
|
|
|
|
@castle_api_router.post("/api/v1/entries/revenue", status_code=HTTPStatus.CREATED)
|
|
async def api_create_revenue_entry(
|
|
data: RevenueEntry,
|
|
wallet: WalletTypeInfo = Depends(require_admin_key),
|
|
) -> JournalEntry:
|
|
"""
|
|
Create a revenue entry (castle receives payment).
|
|
Admin only.
|
|
"""
|
|
# Get revenue account
|
|
revenue_account = await get_account_by_name(data.revenue_account)
|
|
if not revenue_account:
|
|
revenue_account = await get_account(data.revenue_account)
|
|
if not revenue_account:
|
|
raise HTTPException(
|
|
status_code=HTTPStatus.NOT_FOUND,
|
|
detail=f"Revenue account '{data.revenue_account}' not found",
|
|
)
|
|
|
|
# Get payment method account
|
|
payment_account = await get_account_by_name(data.payment_method_account)
|
|
if not payment_account:
|
|
payment_account = await get_account(data.payment_method_account)
|
|
if not payment_account:
|
|
raise HTTPException(
|
|
status_code=HTTPStatus.NOT_FOUND,
|
|
detail=f"Payment account '{data.payment_method_account}' not found",
|
|
)
|
|
|
|
# Create journal entry
|
|
# DR Cash/Lightning/Bank, CR Revenue
|
|
entry_data = CreateJournalEntry(
|
|
description=data.description,
|
|
reference=data.reference,
|
|
lines=[
|
|
CreateEntryLine(
|
|
account_id=payment_account.id,
|
|
debit=data.amount,
|
|
credit=0,
|
|
description="Payment received",
|
|
),
|
|
CreateEntryLine(
|
|
account_id=revenue_account.id,
|
|
debit=0,
|
|
credit=data.amount,
|
|
description="Revenue earned",
|
|
),
|
|
],
|
|
)
|
|
|
|
return await create_journal_entry(entry_data, wallet.wallet.id)
|
|
|
|
|
|
# ===== USER BALANCE ENDPOINTS =====
|
|
|
|
|
|
@castle_api_router.get("/api/v1/balance")
|
|
async def api_get_my_balance(
|
|
wallet: WalletTypeInfo = Depends(require_invoice_key),
|
|
) -> UserBalance:
|
|
"""Get current user's balance with the Castle"""
|
|
from lnbits.settings import settings as lnbits_settings
|
|
|
|
# If super user, show total castle liabilities (what castle owes to all users)
|
|
if wallet.wallet.user == lnbits_settings.super_user:
|
|
all_balances = await get_all_user_balances()
|
|
total_owed = sum(b.balance for b in all_balances if b.balance > 0)
|
|
|
|
# Aggregate fiat balances from all users
|
|
total_fiat_balances = {}
|
|
for user_balance in all_balances:
|
|
for currency, amount in user_balance.fiat_balances.items():
|
|
if currency not in total_fiat_balances:
|
|
total_fiat_balances[currency] = 0.0
|
|
# Only add positive balances (what castle owes)
|
|
if amount > 0:
|
|
total_fiat_balances[currency] += amount
|
|
|
|
# Return as castle's "balance" - positive means castle owes money
|
|
return UserBalance(
|
|
user_id=wallet.wallet.user,
|
|
balance=total_owed,
|
|
accounts=[],
|
|
fiat_balances=total_fiat_balances,
|
|
)
|
|
|
|
# For regular users, show their individual balance
|
|
return await get_user_balance(wallet.wallet.user)
|
|
|
|
|
|
@castle_api_router.get("/api/v1/balance/{user_id}")
|
|
async def api_get_user_balance(user_id: str) -> UserBalance:
|
|
"""Get a specific user's balance with the Castle"""
|
|
return await get_user_balance(user_id)
|
|
|
|
|
|
@castle_api_router.get("/api/v1/balances/all")
|
|
async def api_get_all_balances(
|
|
wallet: WalletTypeInfo = Depends(require_admin_key),
|
|
) -> list[UserBalance]:
|
|
"""Get all user balances (admin/super user only)"""
|
|
return await get_all_user_balances()
|
|
|
|
|
|
# ===== PAYMENT ENDPOINTS =====
|
|
|
|
|
|
@castle_api_router.post("/api/v1/generate-payment-invoice")
|
|
async def api_generate_payment_invoice(
|
|
data: GeneratePaymentInvoice,
|
|
wallet: WalletTypeInfo = Depends(require_invoice_key),
|
|
) -> dict:
|
|
"""
|
|
Generate an invoice on the Castle wallet for user to pay their balance.
|
|
User can then pay this invoice to settle their debt.
|
|
"""
|
|
from lnbits.core.crud.wallets import get_wallet
|
|
from lnbits.core.models import CreateInvoice
|
|
from lnbits.core.services import create_payment_request
|
|
|
|
# Get castle wallet ID
|
|
castle_wallet_id = await check_castle_wallet_configured()
|
|
|
|
# Create invoice on castle wallet
|
|
invoice_data = CreateInvoice(
|
|
out=False,
|
|
amount=data.amount,
|
|
memo=f"Payment from user {wallet.wallet.user[:8]} to Castle",
|
|
unit="sat",
|
|
extra={"user_id": wallet.wallet.user, "type": "castle_payment"},
|
|
)
|
|
|
|
payment = await create_payment_request(castle_wallet_id, invoice_data)
|
|
|
|
# Get castle wallet to return its inkey for payment checking
|
|
castle_wallet = await get_wallet(castle_wallet_id)
|
|
if not castle_wallet:
|
|
raise HTTPException(
|
|
status_code=HTTPStatus.NOT_FOUND, detail="Castle wallet not found"
|
|
)
|
|
|
|
return {
|
|
"payment_hash": payment.payment_hash,
|
|
"payment_request": payment.bolt11,
|
|
"amount": data.amount,
|
|
"memo": invoice_data.memo,
|
|
"check_wallet_key": castle_wallet.inkey, # Key to check payment status
|
|
}
|
|
|
|
|
|
@castle_api_router.post("/api/v1/record-payment")
|
|
async def api_record_payment(
|
|
data: RecordPayment,
|
|
wallet: WalletTypeInfo = Depends(require_invoice_key),
|
|
) -> dict:
|
|
"""
|
|
Record a lightning payment in accounting after invoice is paid.
|
|
This reduces what the user owes to the castle.
|
|
"""
|
|
from lnbits.core.crud.payments import get_standalone_payment
|
|
|
|
# Get the payment details
|
|
payment = await get_standalone_payment(data.payment_hash)
|
|
if not payment:
|
|
raise HTTPException(
|
|
status_code=HTTPStatus.NOT_FOUND, detail="Payment not found"
|
|
)
|
|
|
|
if not payment.paid:
|
|
raise HTTPException(
|
|
status_code=HTTPStatus.BAD_REQUEST, detail="Payment not yet paid"
|
|
)
|
|
|
|
# Get user's receivable account (what user owes)
|
|
user_receivable = await get_or_create_user_account(
|
|
wallet.wallet.user, AccountType.ASSET, "Accounts Receivable"
|
|
)
|
|
|
|
# Get lightning account
|
|
lightning_account = await get_account_by_name("Lightning Balance")
|
|
if not lightning_account:
|
|
raise HTTPException(
|
|
status_code=HTTPStatus.NOT_FOUND, detail="Lightning account not found"
|
|
)
|
|
|
|
# Create journal entry to record payment
|
|
# DR Lightning Balance, CR Accounts Receivable (User)
|
|
# This reduces what the user owes
|
|
entry_data = CreateJournalEntry(
|
|
description=f"Lightning payment from user {wallet.wallet.user[:8]}",
|
|
reference=data.payment_hash,
|
|
lines=[
|
|
CreateEntryLine(
|
|
account_id=lightning_account.id,
|
|
debit=payment.amount,
|
|
credit=0,
|
|
description="Lightning payment received",
|
|
),
|
|
CreateEntryLine(
|
|
account_id=user_receivable.id,
|
|
debit=0,
|
|
credit=payment.amount,
|
|
description="Payment applied to balance",
|
|
),
|
|
],
|
|
)
|
|
|
|
entry = await create_journal_entry(entry_data, wallet.wallet.user)
|
|
|
|
# Get updated balance
|
|
balance = await get_user_balance(wallet.wallet.user)
|
|
|
|
return {
|
|
"journal_entry_id": entry.id,
|
|
"new_balance": balance.balance,
|
|
"message": "Payment recorded successfully",
|
|
}
|
|
|
|
|
|
@castle_api_router.post("/api/v1/pay-user")
|
|
async def api_pay_user(
|
|
user_id: str,
|
|
amount: int,
|
|
wallet: WalletTypeInfo = Depends(require_admin_key),
|
|
) -> dict:
|
|
"""
|
|
Record a payment from castle to user (reduces what castle owes user).
|
|
Admin only.
|
|
"""
|
|
# Get user's payable account (what castle owes)
|
|
user_payable = await get_or_create_user_account(
|
|
user_id, AccountType.LIABILITY, "Accounts Payable"
|
|
)
|
|
|
|
# Get lightning account
|
|
lightning_account = await get_account_by_name("Lightning Balance")
|
|
if not lightning_account:
|
|
raise HTTPException(
|
|
status_code=HTTPStatus.NOT_FOUND, detail="Lightning account not found"
|
|
)
|
|
|
|
# Create journal entry
|
|
# DR Accounts Payable (User), CR Lightning Balance
|
|
entry_data = CreateJournalEntry(
|
|
description=f"Payment to user {user_id[:8]}",
|
|
lines=[
|
|
CreateEntryLine(
|
|
account_id=user_payable.id,
|
|
debit=amount,
|
|
credit=0,
|
|
description="Payment made to user",
|
|
),
|
|
CreateEntryLine(
|
|
account_id=lightning_account.id,
|
|
debit=0,
|
|
credit=amount,
|
|
description="Lightning payment sent",
|
|
),
|
|
],
|
|
)
|
|
|
|
entry = await create_journal_entry(entry_data, wallet.wallet.id)
|
|
|
|
# Get updated balance
|
|
balance = await get_user_balance(user_id)
|
|
|
|
return {
|
|
"journal_entry": entry.dict(),
|
|
"new_balance": balance.balance,
|
|
"message": "Payment recorded successfully",
|
|
}
|
|
|
|
|
|
# ===== SETTINGS ENDPOINTS =====
|
|
|
|
|
|
@castle_api_router.get("/api/v1/settings")
|
|
async def api_get_settings(
|
|
user: User = Depends(check_user_exists),
|
|
) -> CastleSettings:
|
|
"""Get Castle settings"""
|
|
user_id = "admin"
|
|
settings = await get_settings(user_id)
|
|
# Return empty settings if not configured (so UI can show setup screen)
|
|
if not settings:
|
|
return CastleSettings()
|
|
return settings
|
|
|
|
|
|
@castle_api_router.put("/api/v1/settings")
|
|
async def api_update_settings(
|
|
data: CastleSettings,
|
|
user: User = Depends(check_super_user),
|
|
) -> CastleSettings:
|
|
"""Update Castle settings (super user only)"""
|
|
if not data.castle_wallet_id:
|
|
raise HTTPException(
|
|
status_code=HTTPStatus.BAD_REQUEST,
|
|
detail="Castle wallet ID is required",
|
|
)
|
|
user_id = "admin"
|
|
return await update_settings(user_id, data)
|
|
|
|
|
|
# ===== USER WALLET ENDPOINTS =====
|
|
|
|
|
|
@castle_api_router.get("/api/v1/users")
|
|
async def api_get_all_users(
|
|
wallet: WalletTypeInfo = Depends(require_admin_key),
|
|
) -> list[dict]:
|
|
"""Get all users who have configured their wallet (admin only)"""
|
|
from lnbits.core.crud.users import get_user
|
|
|
|
user_settings = await get_all_user_wallet_settings()
|
|
|
|
users = []
|
|
for setting in user_settings:
|
|
# Get user details from core
|
|
user = await get_user(setting.id)
|
|
|
|
# Use username if available, otherwise truncate user_id
|
|
username = user.username if user and user.username else setting.id[:16] + "..."
|
|
|
|
users.append({
|
|
"user_id": setting.id,
|
|
"user_wallet_id": setting.user_wallet_id,
|
|
"username": username,
|
|
})
|
|
|
|
return users
|
|
|
|
|
|
@castle_api_router.get("/api/v1/user/wallet")
|
|
async def api_get_user_wallet(
|
|
user: User = Depends(check_user_exists),
|
|
) -> UserWalletSettings:
|
|
"""Get current user's wallet settings"""
|
|
from lnbits.settings import settings as lnbits_settings
|
|
|
|
# If user is super user, return the castle wallet
|
|
if user.id == lnbits_settings.super_user:
|
|
castle_settings = await get_settings("admin")
|
|
if castle_settings and castle_settings.castle_wallet_id:
|
|
return UserWalletSettings(user_wallet_id=castle_settings.castle_wallet_id)
|
|
return UserWalletSettings()
|
|
|
|
# For regular users, get their personal wallet
|
|
settings = await get_user_wallet(user.id)
|
|
# Return empty settings if not configured (so UI can show setup screen)
|
|
if not settings:
|
|
return UserWalletSettings()
|
|
return settings
|
|
|
|
|
|
@castle_api_router.put("/api/v1/user/wallet")
|
|
async def api_update_user_wallet(
|
|
data: UserWalletSettings,
|
|
user: User = Depends(check_user_exists),
|
|
) -> UserWalletSettings:
|
|
"""Update current user's wallet settings"""
|
|
from lnbits.settings import settings as lnbits_settings
|
|
|
|
# Super user cannot set their wallet separately - it's always the castle wallet
|
|
if user.id == lnbits_settings.super_user:
|
|
raise HTTPException(
|
|
status_code=HTTPStatus.FORBIDDEN,
|
|
detail="Super user wallet is automatically set to the Castle wallet. Update Castle settings instead.",
|
|
)
|
|
|
|
if not data.user_wallet_id:
|
|
raise HTTPException(
|
|
status_code=HTTPStatus.BAD_REQUEST,
|
|
detail="User wallet ID is required",
|
|
)
|
|
return await update_user_wallet(user.id, data)
|