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.

This commit is contained in:
padreug 2025-06-22 12:25:50 +02:00
parent edb0b4d05e
commit 32e8f31b82
2 changed files with 131 additions and 65 deletions

View file

@ -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)

View file

@ -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}