chore: hygiene sweep — dead code, role race, user lookup, stale files

One pass over the LOW-tier review items plus two folded issues:

- Delete validate_journal_entry (dead since the Fava migration; it
  validated the pre-string-amount model) with its exports, unused
  crud imports, and tests. Beancount validates entries now.
- Migration m006: UNIQUE index on user_roles(user_id, role_id) after
  deduping; assign_user_role inserts with ON CONFLICT DO NOTHING and
  returns the existing assignment — closes the auto-assign
  check-then-act race on concurrent logins.
- Extract _get_username_from_user_id (110 lines in views_api, fresh
  LNbits Database per call inside per-row hot paths) into
  user_lookup.py with one shared core-DB handle, a 60s TTL cache and
  a batch get_usernames API (review #18).
- Receivable-entry responses report CLEARED, matching the flag the
  formatter actually writes; PENDING misled the UI (libra-#35).
- Replace the remaining print() calls in tasks.py with logger.
- get_all_accounts derives valid roots from
  account_utils.ACCOUNT_TYPE_ROOTS instead of a hardcoded tuple, and
  the no-op per-test rate-limit reset is gone (libra-#54).
- Delete migrations_old.py.bak, MIGRATION_SQUASH_SUMMARY.md,
  docs/PHASE*_COMPLETE.md and the rendered .html; .gitignore data/
  (it holds the runtime .lnbits_auth_key secret).
- Track docs/CODE-REVIEW-2026-06.md with finding statuses updated for
  the PR #55-#59 + chore/hygiene series.
- CLAUDE.md notes LNbits pins Pydantic v1: keep .dict(), don't
  "modernize" to .model_dump().

Note: format_payment_entry's is_payable docstring (flagged in review
follow-up) turned out to be consistent with the body — no change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Padreug 2026-07-12 16:01:04 +02:00
commit ec6cac51f0
19 changed files with 454 additions and 2942 deletions

View file

@ -15,6 +15,7 @@ from lnbits.utils.exchange_rates import allowed_currencies, fiat_amount_as_satos
from .account_utils import VALID_ACCOUNT_PREFIXES, validate_account_name
from .beancount_format import fiat_rate_metadata
from .user_lookup import get_username
from .crud import (
approve_manual_payment_request,
check_balance_assertion,
@ -643,7 +644,7 @@ async def api_get_user_entries(
break
# Look up actual username using helper function
username = await _get_username_from_user_id(user_id_match) if user_id_match else None
username = await get_username(user_id_match) if user_id_match else None
entry_data = {
"id": entry_id or e.get("entry_hash", "unknown"),
@ -684,119 +685,6 @@ async def api_get_user_entries(
}
async def _get_username_from_user_id(user_id: str) -> str:
"""
Helper function to get username from user_id, handling various formats.
Supports:
- Full UUID with dashes (36 chars): "375ec158-686c-4a21-b44d-a51cc90ef07d"
- Dashless UUID (32 chars): "375ec158686c4a21b44da51cc90ef07d"
- Partial ID (8 chars from account names): "375ec158"
Returns username or formatted fallback.
"""
from lnbits.core.crud.users import get_user
logger.debug(f"[USERNAME] Called with: '{user_id}' (len={len(user_id) if user_id else 0})")
if not user_id:
return None
# Case 1: Already in standard UUID format (36 chars with dashes)
if len(user_id) == 36 and user_id.count('-') == 4:
logger.debug(f"[USERNAME] Case 1: Full UUID format")
user = await get_user(user_id)
result = user.username if user and user.username else f"User-{user_id[:8]}"
logger.debug(f"[USERNAME] Case 1 result: '{result}'")
return result
# Case 2: Dashless 32-char UUID - lookup via Libra user settings, fallback to LNbits
elif len(user_id) == 32 and '-' not in user_id:
logger.debug(f"[USERNAME] Case 2: Dashless UUID format - looking up in Libra user settings")
try:
# Convert dashless to dashed format
user_id_with_dashes = f"{user_id[0:8]}-{user_id[8:12]}-{user_id[12:16]}-{user_id[16:20]}-{user_id[20:32]}"
logger.debug(f"[USERNAME] Converted to dashed format: {user_id_with_dashes}")
# Try Libra settings first
user_settings = await get_all_user_wallet_settings()
for setting in user_settings:
if setting.id == user_id_with_dashes:
logger.debug(f"[USERNAME] Found matching user in Libra settings")
user = await get_user(setting.id)
result = user.username if user and user.username else f"User-{user_id[:8]}"
logger.debug(f"[USERNAME] Case 2 result (from Libra): '{result}'")
return result
# Not in Libra settings - try LNbits database directly
logger.debug(f"[USERNAME] Not in Libra settings, querying LNbits database directly")
from lnbits.db import Database
db = Database("database")
async with db.connect() as conn:
row = await conn.fetchone(
"SELECT id, username FROM accounts WHERE id = :user_id LIMIT 1",
{"user_id": user_id_with_dashes}
)
logger.debug(f"[USERNAME] Database query result: {row}")
if row and row["username"]:
result = row["username"]
logger.debug(f"[USERNAME] Case 2 result (from LNbits DB): '{result}'")
return result
# User doesn't exist anywhere
logger.debug(f"[USERNAME] User not found in LNbits database either")
result = f"User-{user_id[:8]}"
logger.debug(f"[USERNAME] Case 2 result (not found): '{result}'")
return result
except Exception as e:
logger.error(f"Error looking up user by dashless UUID {user_id}: {e}")
result = f"User-{user_id[:8]}"
return result
# Case 3: Partial ID (8 chars from account name) - lookup via Libra user settings
elif len(user_id) == 8:
logger.debug(f"[USERNAME] Case 3: Partial ID format - looking up in Libra user settings")
try:
# Get all Libra users (which have full user_ids)
user_settings = await get_all_user_wallet_settings()
# Find matching user by first 8 chars
for setting in user_settings:
if setting.id.startswith(user_id):
logger.debug(f"[USERNAME] Found full user_id: {setting.id}")
# Now get username from LNbits with full ID
user = await get_user(setting.id)
result = user.username if user and user.username else f"User-{user_id}"
logger.debug(f"[USERNAME] Case 3 result (found): '{result}'")
return result
# No matching user found in Libra settings
logger.debug(f"[USERNAME] No matching user found in Libra settings")
result = f"User-{user_id}"
logger.debug(f"[USERNAME] Case 3 result (not found): '{result}'")
return result
except Exception as e:
logger.error(f"Error looking up user by partial ID {user_id}: {e}")
result = f"User-{user_id}"
return result
# Case 4: Unknown format - try as-is and fall back
else:
logger.debug(f"[USERNAME] Case 4: Unknown format - trying as-is")
try:
user = await get_user(user_id)
result = user.username if user and user.username else f"User-{user_id[:8]}"
logger.debug(f"[USERNAME] Case 4 result: '{result}'")
return result
except Exception as e:
logger.debug(f"[USERNAME] Case 4 exception: {e}")
result = f"User-{user_id[:8]}"
logger.debug(f"[USERNAME] Case 4 fallback result: '{result}'")
return result
@libra_api_router.get("/api/v1/entries/pending")
async def api_get_pending_entries(
auth: AuthContext = Depends(require_super_user),
@ -844,7 +732,7 @@ async def api_get_pending_entries(
break
# Look up username using helper function
username = await _get_username_from_user_id(user_id) if user_id else None
username = await get_username(user_id) if user_id else None
# Extract amount from postings (sum of absolute values / 2)
amount_sats = 0
@ -1453,7 +1341,9 @@ async def api_create_receivable_entry(
created_by=auth.user_id,
created_at=datetime.now(),
reference=data.reference,
flag=JournalEntryFlag.PENDING,
# Receivables are written cleared (format_receivable_entry uses
# flag="*") — reporting PENDING here misled the UI (libra-#35).
flag=JournalEntryFlag.CLEARED,
meta=entry_meta,
lines=[
EntryLine(
@ -1682,7 +1572,7 @@ async def api_get_all_balances(
# Enrich with username information using helper function
result = []
for balance in balances:
username = await _get_username_from_user_id(balance["user_id"])
username = await get_username(balance["user_id"])
result.append({
"user_id": balance["user_id"],