From 32e8f31b825f5ad331ae035ef86dc07db325d5ac Mon Sep 17 00:00:00 2001 From: padreug Date: Sun, 22 Jun 2025 12:25:50 +0200 Subject: [PATCH] Refactor client API endpoints for DCA dashboard: Update endpoint structure to focus on client-specific functionalities, including dashboard summary, transaction history, and analytics. Enhance code readability with improved formatting and add support for exporting transaction data in CSV format. --- __init__.py | 14 ++-- views_api.py | 182 ++++++++++++++++++++++++++++++++++----------------- 2 files changed, 131 insertions(+), 65 deletions(-) diff --git a/__init__.py b/__init__.py index 69f726d..7064691 100644 --- a/__init__.py +++ b/__init__.py @@ -15,7 +15,9 @@ logger.debug( ) -satmachineclient_ext: APIRouter = APIRouter(prefix="/satmachineclient", tags=["DCA Client"]) +satmachineclient_ext: APIRouter = APIRouter( + prefix="/satmachineclient", tags=["DCA Client"] +) satmachineclient_ext.include_router(satmachineclient_generic_router) satmachineclient_ext.include_router(satmachineclient_api_router) @@ -39,11 +41,15 @@ def satmachineclient_stop(): def satmachineclient_start(): # Start invoice listener task - invoice_task = create_permanent_unique_task("ext_satmachineclient", wait_for_paid_invoices) + invoice_task = create_permanent_unique_task( + "ext_satmachineclient", wait_for_paid_invoices + ) scheduled_tasks.append(invoice_task) - + # Start hourly transaction polling task - polling_task = create_permanent_unique_task("ext_satmachineclient_polling", hourly_transaction_polling) + polling_task = create_permanent_unique_task( + "ext_satmachineclient_polling", hourly_transaction_polling + ) scheduled_tasks.append(polling_task) diff --git a/views_api.py b/views_api.py index aac2b08..4bcfffe 100644 --- a/views_api.py +++ b/views_api.py @@ -1,87 +1,147 @@ -# Description: This file contains the extensions API endpoints. +# Description: Client-focused API endpoints for DCA dashboard from http import HTTPStatus -from typing import Optional +from typing import List, Optional +from datetime import datetime, timedelta -from fastapi import APIRouter, Depends, Request -from lnbits.core.crud import get_user +from fastapi import APIRouter, Depends, Query from lnbits.core.models import WalletTypeInfo -from lnbits.core.services import create_invoice -from lnbits.decorators import require_admin_key +from lnbits.decorators import require_invoice_key from starlette.exceptions import HTTPException from .crud import ( - # DCA CRUD operations - create_dca_client, - get_dca_client, - get_all_deposits, - get_deposit, - get_client_balance_summary, + get_client_dashboard_summary, + get_client_transactions, + get_client_analytics, + update_client_dca_settings, + get_client_by_user_id, ) from .models import ( - # DCA models - CreateDcaClientData, - DcaClient, - DcaDeposit, - ClientBalanceSummary, + ClientDashboardSummary, + ClientTransaction, + ClientAnalytics, + UpdateClientSettings, ) satmachineclient_api_router = APIRouter() ################################################### -################ DCA API ENDPOINTS ################ +############## CLIENT DASHBOARD API ############### ################################################### -# DCA Client Endpoints -# Note: Client creation/update +@satmachineclient_api_router.get("/api/v1/dashboard/summary") +async def api_get_dashboard_summary( + wallet: WalletTypeInfo = Depends(require_invoice_key), +) -> ClientDashboardSummary: + """Get client dashboard summary metrics""" + summary = await get_client_dashboard_summary(wallet.user) + if not summary: + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, + detail="Client data not found" + ) + return summary -@satmachineclient_api_router.post("/api/v1/dca/clients", status_code=HTTPStatus.CREATED) -async def api_create_test_dca_client( - data: CreateDcaClientData, - wallet: WalletTypeInfo = Depends(require_admin_key), -) -> DcaClient: - return await create_dca_client(data) +@satmachineclient_api_router.get("/api/v1/dashboard/transactions") +async def api_get_client_transactions( + wallet: WalletTypeInfo = Depends(require_invoice_key), + limit: int = Query(50, ge=1, le=1000), + offset: int = Query(0, ge=0), + transaction_type: Optional[str] = Query(None), + start_date: Optional[datetime] = Query(None), + end_date: Optional[datetime] = Query(None), +) -> List[ClientTransaction]: + """Get client's DCA transaction history with filtering""" + return await get_client_transactions( + wallet.user, + limit=limit, + offset=offset, + transaction_type=transaction_type, + start_date=start_date, + end_date=end_date + ) -@satmachineclient_api_router.get("/api/v1/dca/clients/{client_id}/balance") -async def api_get_client_balance( - client_id: str, - wallet: WalletTypeInfo = Depends(require_admin_key), -) -> ClientBalanceSummary: - """Get client balance summary""" - client = await get_dca_client(client_id) +@satmachineclient_api_router.get("/api/v1/dashboard/analytics") +async def api_get_client_analytics( + wallet: WalletTypeInfo = Depends(require_invoice_key), + time_range: str = Query("30d", regex="^(7d|30d|90d|1y|all)$"), +) -> ClientAnalytics: + """Get client performance analytics and cost basis data""" + analytics = await get_client_analytics(wallet.user, time_range) + if not analytics: + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, + detail="Analytics data not available" + ) + return analytics + + +@satmachineclient_api_router.put("/api/v1/dashboard/settings") +async def api_update_client_settings( + settings: UpdateClientSettings, + wallet: WalletTypeInfo = Depends(require_invoice_key), +) -> dict: + """Update client DCA settings (mode, limits, status)""" + client = await get_client_by_user_id(wallet.user) if not client: raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="DCA client not found." + status_code=HTTPStatus.NOT_FOUND, + detail="Client profile not found" ) - - return await get_client_balance_summary(client_id) - - -# DCA Deposit Endpoints - - -# NOTE: to Claude - modify this so it only gets the deposits for the user! important security -@satmachineclient_api_router.get("/api/v1/dca/deposits") -async def api_get_deposits( - wallet: WalletTypeInfo = Depends(require_admin_key), -) -> list[DcaDeposit]: - """Get all deposits""" - return await get_all_deposits() - - -# NOTE: does the client have any need to get sepcific deposits? -@satmachineclient_api_router.get("/api/v1/dca/deposits/{deposit_id}") -async def api_get_deposit( - deposit_id: str, - wallet: WalletTypeInfo = Depends(require_admin_key), -) -> DcaDeposit: - """Get a specific deposit""" - deposit = await get_deposit(deposit_id) - if not deposit: + + success = await update_client_dca_settings(client.id, settings) + if not success: raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Deposit not found." + status_code=HTTPStatus.BAD_REQUEST, + detail="Failed to update settings" ) - return deposit + + return {"message": "Settings updated successfully"} + + +@satmachineclient_api_router.get("/api/v1/dashboard/export/transactions") +async def api_export_transactions( + wallet: WalletTypeInfo = Depends(require_invoice_key), + format: str = Query("csv", regex="^(csv|json)$"), + start_date: Optional[datetime] = Query(None), + end_date: Optional[datetime] = Query(None), +): + """Export client transaction history""" + transactions = await get_client_transactions( + wallet.user, + limit=10000, # Large limit for export + start_date=start_date, + end_date=end_date + ) + + if format == "csv": + # Return CSV response + from io import StringIO + import csv + + output = StringIO() + writer = csv.writer(output) + writer.writerow(['Date', 'Amount (Sats)', 'Amount (Fiat)', 'Exchange Rate', 'Type', 'Status']) + + for tx in transactions: + writer.writerow([ + tx.created_at.isoformat(), + tx.amount_sats, + tx.amount_fiat / 100, # Convert centavos to full currency + tx.exchange_rate, + tx.transaction_type, + tx.status + ]) + + from fastapi.responses import StreamingResponse + output.seek(0) + return StreamingResponse( + iter([output.getvalue()]), + media_type="text/csv", + headers={"Content-Disposition": "attachment; filename=dca_transactions.csv"} + ) + else: + return {"transactions": transactions}