From 63d851ce94ba22952c7f500abcbf8cee3ab8496d Mon Sep 17 00:00:00 2001 From: padreug Date: Mon, 10 Nov 2025 00:47:52 +0100 Subject: [PATCH] Refactors user entry retrieval from Fava Switches to retrieving all journal entries from Fava and filtering in the application to allow filtering by account type and user. This provides more flexibility and control over the data being presented to the user. Also extracts and includes relevant metadata such as entry ID, fiat amounts, and references for improved frontend display. --- views_api.py | 149 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 121 insertions(+), 28 deletions(-) diff --git a/views_api.py b/views_api.py index 25d49db..4611b60 100644 --- a/views_api.py +++ b/views_api.py @@ -315,38 +315,131 @@ async def api_get_user_entries( # Regular user can only see their own entries target_user_id = wallet.wallet.user - # Query Fava for transactions - if target_user_id: - # Build account pattern based on account_type filter - if filter_account_type: - # Filter by account type (asset = receivable, liability = payable) - if filter_account_type.lower() == "asset": - account_pattern = f"Receivable:User-{target_user_id[:8]}" - elif filter_account_type.lower() == "liability": - account_pattern = f"Payable:User-{target_user_id[:8]}" - else: - account_pattern = f"User-{target_user_id[:8]}" + # Get all journal entries from Fava (full transaction objects) + all_entries = await fava.get_journal_entries() + + # Filter and transform entries + filtered_entries = [] + for e in all_entries: + if e.get("t") != "Transaction": + continue + + # Skip voided transactions + if "voided" in e.get("tags", []): + continue + + # Extract user ID from metadata or account names + user_id_match = None + entry_meta = e.get("meta", {}) + if "user-id" in entry_meta: + user_id_match = entry_meta["user-id"] else: - # All user accounts - account_pattern = f"User-{target_user_id[:8]}" + # Try to extract from account names in postings + for posting in e.get("postings", []): + account = posting.get("account", "") + if "User-" in account: + # Extract user ID from account name (e.g., "Liabilities:Payable:User-abc123") + parts = account.split("User-") + if len(parts) > 1: + user_id_match = parts[1] # Just the short ID after User- + break - entries = await fava.query_transactions( - account_pattern=account_pattern, - limit=limit + offset # Fava doesn't support offset, so fetch more and slice - ) - # Apply offset - entries = entries[offset:offset + limit] - total = len(entries) # Note: This is approximate since we don't know the true total - else: - # Super user viewing all entries - entries = await fava.query_transactions(limit=limit + offset) - entries = entries[offset:offset + limit] - total = len(entries) + # Filter by target user if specified + if target_user_id and user_id_match: + if not user_id_match.startswith(target_user_id[:8]): + continue + + # Filter by account type if specified + if filter_account_type and user_id_match: + postings = e.get("postings", []) + has_matching_account = False + for posting in postings: + account = posting.get("account", "") + if filter_account_type.lower() == "asset" and "Receivable" in account: + has_matching_account = True + break + elif filter_account_type.lower() == "liability" and "Payable" in account: + has_matching_account = True + break + if not has_matching_account: + continue + + # Extract data for frontend + # Extract entry ID from links + entry_id = None + links = e.get("links", []) + if isinstance(links, (list, set)): + for link in links: + if isinstance(link, str): + link_clean = link.lstrip('^') + if "castle-" in link_clean: + parts = link_clean.split("castle-") + if len(parts) > 1: + entry_id = parts[-1] + break + + # Extract amount from postings + amount_sats = 0 + fiat_amount = None + fiat_currency = None + + postings = e.get("postings", []) + if postings: + first_posting = postings[0] + if isinstance(first_posting, dict): + amount_field = first_posting.get("amount") + if isinstance(amount_field, dict): + amount_sats = abs(int(float(amount_field.get("number", 0)))) + + cost = first_posting.get("cost") + if isinstance(cost, dict): + fiat_amount = float(cost.get("number", 0)) + fiat_currency = cost.get("currency") + + # Extract reference from links (first non-castle link) + reference = None + if isinstance(links, (list, set)): + for link in links: + if isinstance(link, str): + link_clean = link.lstrip('^') + if not link_clean.startswith("castle-") and not link_clean.startswith("ln-"): + reference = link_clean + break + + # Get username from user ID (first 8 chars for display) + username = f"User-{user_id_match[:8]}" if user_id_match else None + + entry_data = { + "id": entry_id or e.get("entry_hash", "unknown"), + "date": e.get("date", ""), + "entry_date": e.get("date", ""), + "flag": e.get("flag"), + "description": e.get("narration", ""), + "payee": e.get("payee"), + "tags": e.get("tags", []), + "links": links, + "amount": amount_sats, + "user_id": user_id_match, + "username": username, + "reference": reference, + "meta": entry_meta, # Include metadata for frontend + } + + if fiat_amount and fiat_currency: + entry_data["fiat_amount"] = fiat_amount + entry_data["fiat_currency"] = fiat_currency + + filtered_entries.append(entry_data) + + # Sort by date descending + filtered_entries.sort(key=lambda x: x.get("date", ""), reverse=True) + + # Apply pagination + total = len(filtered_entries) + paginated_entries = filtered_entries[offset:offset + limit] - # Fava transactions already contain the data we need - # Metadata includes user-id, account information, etc. return { - "entries": entries, + "entries": paginated_entries, "total": total, "limit": limit, "offset": offset,