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

30
crud.py
View file

@ -1,4 +1,3 @@
import json
from datetime import datetime
from typing import Optional
@ -18,13 +17,9 @@ from .models import (
CreateAccount,
CreateAccountPermission,
CreateBalanceAssertion,
CreateEntryLine,
CreateJournalEntry,
CreateRole,
CreateRolePermission,
CreateUserEquityStatus,
EntryLine,
JournalEntry,
PermissionType,
Role,
RolePermission,
@ -39,16 +34,6 @@ from .models import (
UserWithRoles,
)
# Import core accounting logic
from .core.validation import (
ValidationError,
validate_journal_entry,
validate_balance,
validate_receivable_entry,
validate_expense_entry,
validate_payment_entry,
)
db = Database("ext_libra")
# ===== CACHING =====
@ -1540,10 +1525,15 @@ async def assign_user_role(data: AssignUserRole, granted_by: str) -> UserRole:
notes=data.notes,
)
await db.execute(
# The unique index on (user_id, role_id) makes this insert the
# arbiter against concurrent assignments (e.g. two simultaneous
# logins both auto-assigning the default role). rowcount 0 means
# the assignment already exists — return it (idempotent).
result = await db.execute(
"""
INSERT INTO user_roles (id, user_id, role_id, granted_by, granted_at, expires_at, notes)
VALUES (:id, :user_id, :role_id, :granted_by, :granted_at, :expires_at, :notes)
ON CONFLICT (user_id, role_id) DO NOTHING
""",
{
"id": user_role.id,
@ -1555,6 +1545,14 @@ async def assign_user_role(data: AssignUserRole, granted_by: str) -> UserRole:
"notes": user_role.notes,
},
)
if result.rowcount == 0:
existing = await db.fetchone(
"SELECT * FROM user_roles WHERE user_id = :user_id AND role_id = :role_id",
{"user_id": data.user_id, "role_id": data.role_id},
UserRole,
)
if existing:
return existing
return user_role