mirror of
https://github.com/elisiariocouto/leggen.git
synced 2025-12-13 13:42:19 +00:00
This major update transforms leggen from CLI-only to a web-ready architecture while maintaining full CLI compatibility. New Features: - FastAPI backend service (leggend) with comprehensive REST API - Background job scheduler with configurable cron (replaces Ofelia) - All CLI commands refactored to use API endpoints - Docker configuration updated for new services - API client with health checks and error handling API Endpoints: - /api/v1/banks/* - Bank connections and institutions - /api/v1/accounts/* - Account management and balances - /api/v1/transactions/* - Transaction retrieval with filtering - /api/v1/sync/* - Manual sync and scheduler configuration - /api/v1/notifications/* - Notification settings management CLI Enhancements: - New --api-url option and LEGGEND_API_URL environment variable - Enhanced sync command with --wait and --force options - Improved transactions command with --full and --limit options - Automatic fallback and health checking Breaking Changes: - compose.yml structure updated (leggend service added) - Ofelia scheduler removed (internal scheduler used instead) 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
200 lines
8.2 KiB
Python
200 lines
8.2 KiB
Python
from typing import List, Optional
|
|
from fastapi import APIRouter, HTTPException, Query
|
|
from loguru import logger
|
|
|
|
from leggend.api.models.common import APIResponse
|
|
from leggend.api.models.accounts import AccountDetails, AccountBalance, Transaction, TransactionSummary
|
|
from leggend.services.gocardless_service import GoCardlessService
|
|
from leggend.services.database_service import DatabaseService
|
|
|
|
router = APIRouter()
|
|
gocardless_service = GoCardlessService()
|
|
database_service = DatabaseService()
|
|
|
|
|
|
@router.get("/accounts", response_model=APIResponse)
|
|
async def get_all_accounts() -> APIResponse:
|
|
"""Get all connected accounts"""
|
|
try:
|
|
requisitions_data = await gocardless_service.get_requisitions()
|
|
|
|
all_accounts = set()
|
|
for req in requisitions_data.get("results", []):
|
|
all_accounts.update(req.get("accounts", []))
|
|
|
|
accounts = []
|
|
for account_id in all_accounts:
|
|
try:
|
|
account_details = await gocardless_service.get_account_details(account_id)
|
|
balances_data = await gocardless_service.get_account_balances(account_id)
|
|
|
|
# Process balances
|
|
balances = []
|
|
for balance in balances_data.get("balances", []):
|
|
balance_amount = balance["balanceAmount"]
|
|
balances.append(AccountBalance(
|
|
amount=float(balance_amount["amount"]),
|
|
currency=balance_amount["currency"],
|
|
balance_type=balance["balanceType"],
|
|
last_change_date=balance.get("lastChangeDateTime")
|
|
))
|
|
|
|
accounts.append(AccountDetails(
|
|
id=account_details["id"],
|
|
institution_id=account_details["institution_id"],
|
|
status=account_details["status"],
|
|
iban=account_details.get("iban"),
|
|
name=account_details.get("name"),
|
|
currency=account_details.get("currency"),
|
|
created=account_details["created"],
|
|
last_accessed=account_details.get("last_accessed"),
|
|
balances=balances
|
|
))
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to get details for account {account_id}: {e}")
|
|
continue
|
|
|
|
return APIResponse(
|
|
success=True,
|
|
data=accounts,
|
|
message=f"Retrieved {len(accounts)} accounts"
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to get accounts: {e}")
|
|
raise HTTPException(status_code=500, detail=f"Failed to get accounts: {str(e)}")
|
|
|
|
|
|
@router.get("/accounts/{account_id}", response_model=APIResponse)
|
|
async def get_account_details(account_id: str) -> APIResponse:
|
|
"""Get details for a specific account"""
|
|
try:
|
|
account_details = await gocardless_service.get_account_details(account_id)
|
|
balances_data = await gocardless_service.get_account_balances(account_id)
|
|
|
|
# Process balances
|
|
balances = []
|
|
for balance in balances_data.get("balances", []):
|
|
balance_amount = balance["balanceAmount"]
|
|
balances.append(AccountBalance(
|
|
amount=float(balance_amount["amount"]),
|
|
currency=balance_amount["currency"],
|
|
balance_type=balance["balanceType"],
|
|
last_change_date=balance.get("lastChangeDateTime")
|
|
))
|
|
|
|
account = AccountDetails(
|
|
id=account_details["id"],
|
|
institution_id=account_details["institution_id"],
|
|
status=account_details["status"],
|
|
iban=account_details.get("iban"),
|
|
name=account_details.get("name"),
|
|
currency=account_details.get("currency"),
|
|
created=account_details["created"],
|
|
last_accessed=account_details.get("last_accessed"),
|
|
balances=balances
|
|
)
|
|
|
|
return APIResponse(
|
|
success=True,
|
|
data=account,
|
|
message=f"Account details retrieved for {account_id}"
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to get account details for {account_id}: {e}")
|
|
raise HTTPException(status_code=404, detail=f"Account not found: {str(e)}")
|
|
|
|
|
|
@router.get("/accounts/{account_id}/balances", response_model=APIResponse)
|
|
async def get_account_balances(account_id: str) -> APIResponse:
|
|
"""Get balances for a specific account"""
|
|
try:
|
|
balances_data = await gocardless_service.get_account_balances(account_id)
|
|
|
|
balances = []
|
|
for balance in balances_data.get("balances", []):
|
|
balance_amount = balance["balanceAmount"]
|
|
balances.append(AccountBalance(
|
|
amount=float(balance_amount["amount"]),
|
|
currency=balance_amount["currency"],
|
|
balance_type=balance["balanceType"],
|
|
last_change_date=balance.get("lastChangeDateTime")
|
|
))
|
|
|
|
return APIResponse(
|
|
success=True,
|
|
data=balances,
|
|
message=f"Retrieved {len(balances)} balances for account {account_id}"
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to get balances for account {account_id}: {e}")
|
|
raise HTTPException(status_code=404, detail=f"Failed to get balances: {str(e)}")
|
|
|
|
|
|
@router.get("/accounts/{account_id}/transactions", response_model=APIResponse)
|
|
async def get_account_transactions(
|
|
account_id: str,
|
|
limit: Optional[int] = Query(default=100, le=500),
|
|
offset: Optional[int] = Query(default=0, ge=0),
|
|
summary_only: bool = Query(default=False, description="Return transaction summaries only")
|
|
) -> APIResponse:
|
|
"""Get transactions for a specific account"""
|
|
try:
|
|
account_details = await gocardless_service.get_account_details(account_id)
|
|
transactions_data = await gocardless_service.get_account_transactions(account_id)
|
|
|
|
# Process transactions
|
|
processed_transactions = database_service.process_transactions(
|
|
account_id, account_details, transactions_data
|
|
)
|
|
|
|
# Apply pagination
|
|
total_transactions = len(processed_transactions)
|
|
paginated_transactions = processed_transactions[offset:offset + limit]
|
|
|
|
if summary_only:
|
|
# Return simplified transaction summaries
|
|
summaries = [
|
|
TransactionSummary(
|
|
internal_transaction_id=txn["internalTransactionId"],
|
|
date=txn["transactionDate"],
|
|
description=txn["description"],
|
|
amount=txn["transactionValue"],
|
|
currency=txn["transactionCurrency"],
|
|
status=txn["transactionStatus"],
|
|
account_id=txn["accountId"]
|
|
)
|
|
for txn in paginated_transactions
|
|
]
|
|
data = summaries
|
|
else:
|
|
# Return full transaction details
|
|
transactions = [
|
|
Transaction(
|
|
internal_transaction_id=txn["internalTransactionId"],
|
|
institution_id=txn["institutionId"],
|
|
iban=txn["iban"],
|
|
account_id=txn["accountId"],
|
|
transaction_date=txn["transactionDate"],
|
|
description=txn["description"],
|
|
transaction_value=txn["transactionValue"],
|
|
transaction_currency=txn["transactionCurrency"],
|
|
transaction_status=txn["transactionStatus"],
|
|
raw_transaction=txn["rawTransaction"]
|
|
)
|
|
for txn in paginated_transactions
|
|
]
|
|
data = transactions
|
|
|
|
return APIResponse(
|
|
success=True,
|
|
data=data,
|
|
message=f"Retrieved {len(data)} transactions (showing {offset + 1}-{offset + len(data)} of {total_transactions})"
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to get transactions for account {account_id}: {e}")
|
|
raise HTTPException(status_code=404, detail=f"Failed to get transactions: {str(e)}") |