Skip to content

presentation.routers.api.v1.transactions

src.presentation.routers.api.v1.transactions

Transactions resource handlers.

Handler functions for transaction management endpoints. Routes are registered via ROUTE_REGISTRY in routes/registry.py.

Handlers

get_transaction - Get transaction details sync_transactions - Sync transactions from provider list_transactions_by_account - List transactions for an account

Reference
  • docs/architecture/api-design-patterns.md
  • docs/architecture/error-handling-architecture.md

Classes

Functions

get_transaction async

get_transaction(
    request: Request,
    current_user: AuthenticatedUser,
    transaction_id: Annotated[
        UUID, Path(description="Transaction UUID")
    ],
    handler: GetTransactionHandler = Depends(
        handler_factory(GetTransactionHandler)
    ),
) -> TransactionResponse | JSONResponse

Get a specific transaction.

GET /api/v1/transactions/{id} → 200 OK

Parameters:

Name Type Description Default
request Request

FastAPI request object.

required
current_user AuthenticatedUser

Authenticated user (from JWT).

required
transaction_id Annotated[UUID, Path(description='Transaction UUID')]

Transaction UUID.

required
handler GetTransactionHandler

Get transaction handler (injected).

Depends(handler_factory(GetTransactionHandler))

Returns:

Type Description
TransactionResponse | JSONResponse

TransactionResponse with transaction details.

TransactionResponse | JSONResponse

JSONResponse with RFC 9457 error on failure.

Source code in src/presentation/routers/api/v1/transactions.py
async def get_transaction(
    request: Request,
    current_user: AuthenticatedUser,
    transaction_id: Annotated[UUID, Path(description="Transaction UUID")],
    handler: GetTransactionHandler = Depends(handler_factory(GetTransactionHandler)),
) -> TransactionResponse | JSONResponse:
    """Get a specific transaction.

    GET /api/v1/transactions/{id} → 200 OK

    Args:
        request: FastAPI request object.
        current_user: Authenticated user (from JWT).
        transaction_id: Transaction UUID.
        handler: Get transaction handler (injected).

    Returns:
        TransactionResponse with transaction details.
        JSONResponse with RFC 9457 error on failure.
    """
    query = GetTransaction(
        transaction_id=transaction_id,
        user_id=current_user.user_id,
    )
    result = await handler.handle(query)

    if isinstance(result, Failure):
        app_error = _map_transaction_error(result.error)
        return ErrorResponseBuilder.from_application_error(
            error=app_error,
            request=request,
            trace_id=get_trace_id() or "",
        )

    return TransactionResponse.from_dto(result.value)

sync_transactions async

sync_transactions(
    request: Request,
    current_user: AuthenticatedUser,
    data: SyncTransactionsRequest,
    handler: SyncTransactionsHandler = Depends(
        handler_factory(SyncTransactionsHandler)
    ),
) -> SyncTransactionsResponse | JSONResponse

Sync transactions from a provider connection.

POST /api/v1/transactions/syncs → 201 Created

Fetches transactions from the provider and upserts them to the database. Returns sync statistics (created/updated counts).

Parameters:

Name Type Description Default
request Request

FastAPI request object.

required
current_user AuthenticatedUser

Authenticated user (from JWT).

required
data SyncTransactionsRequest

Sync request with connection_id and optional filters.

required
handler SyncTransactionsHandler

Sync transactions handler (injected).

Depends(handler_factory(SyncTransactionsHandler))

Returns:

Type Description
SyncTransactionsResponse | JSONResponse

SyncTransactionsResponse with sync statistics.

SyncTransactionsResponse | JSONResponse

JSONResponse with RFC 9457 error on failure.

Source code in src/presentation/routers/api/v1/transactions.py
async def sync_transactions(
    request: Request,
    current_user: AuthenticatedUser,
    data: SyncTransactionsRequest,
    handler: SyncTransactionsHandler = Depends(
        handler_factory(SyncTransactionsHandler)
    ),
) -> SyncTransactionsResponse | JSONResponse:
    """Sync transactions from a provider connection.

    POST /api/v1/transactions/syncs → 201 Created

    Fetches transactions from the provider and upserts them to the database.
    Returns sync statistics (created/updated counts).

    Args:
        request: FastAPI request object.
        current_user: Authenticated user (from JWT).
        data: Sync request with connection_id and optional filters.
        handler: Sync transactions handler (injected).

    Returns:
        SyncTransactionsResponse with sync statistics.
        JSONResponse with RFC 9457 error on failure.
    """
    command = SyncTransactions(
        user_id=current_user.user_id,
        connection_id=data.connection_id,
        account_id=data.account_id,
        start_date=data.start_date,
        end_date=data.end_date,
        force=data.force,
    )
    result = await handler.handle(command)

    if isinstance(result, Failure):
        app_error = _map_transaction_error(result.error)
        return ErrorResponseBuilder.from_application_error(
            error=app_error,
            request=request,
            trace_id=get_trace_id() or "",
        )

    # Convert handler result to response
    sync_result = result.value
    return SyncTransactionsResponse(
        created=sync_result.created,
        updated=sync_result.updated,
        unchanged=sync_result.unchanged,
        errors=sync_result.errors,
        message=sync_result.message,
        transactions_created=sync_result.created,
        transactions_updated=sync_result.updated,
    )

list_transactions_by_account async

list_transactions_by_account(
    request: Request,
    current_user: AuthenticatedUser,
    account_id: Annotated[
        UUID, Path(description="Account UUID")
    ],
    limit: Annotated[
        int,
        Query(
            description="Maximum number of results",
            ge=1,
            le=100,
        ),
    ] = 50,
    offset: Annotated[
        int,
        Query(
            description="Number of results to skip", ge=0
        ),
    ] = 0,
    transaction_type: Annotated[
        str | None,
        Query(
            description="Filter by transaction type (e.g., trade, transfer)"
        ),
    ] = None,
    start_date: Annotated[
        date | None,
        Query(
            description="Filter by start date (inclusive)"
        ),
    ] = None,
    end_date: Annotated[
        date | None,
        Query(description="Filter by end date (inclusive)"),
    ] = None,
    account_handler: ListTransactionsByAccountHandler = Depends(
        handler_factory(ListTransactionsByAccountHandler)
    ),
    date_handler: ListTransactionsByDateRangeHandler = Depends(
        handler_factory(ListTransactionsByDateRangeHandler)
    ),
) -> TransactionListResponse | JSONResponse

List transactions for a specific account.

GET /api/v1/accounts/{id}/transactions → 200 OK

Supports pagination via limit/offset and filtering by: - transaction_type: Filter to specific type (e.g., "trade") - start_date/end_date: Date range filter

Parameters:

Name Type Description Default
request Request

FastAPI request object.

required
current_user AuthenticatedUser

Authenticated user (from JWT).

required
account_id Annotated[UUID, Path(description='Account UUID')]

Account UUID.

required
limit Annotated[int, Query(description='Maximum number of results', ge=1, le=100)]

Maximum results to return.

50
offset Annotated[int, Query(description='Number of results to skip', ge=0)]

Number of results to skip.

0
transaction_type Annotated[str | None, Query(description='Filter by transaction type (e.g., trade, transfer)')]

Optional type filter.

None
start_date Annotated[date | None, Query(description='Filter by start date (inclusive)')]

Optional start date filter.

None
end_date Annotated[date | None, Query(description='Filter by end date (inclusive)')]

Optional end date filter.

None
account_handler ListTransactionsByAccountHandler

List by account handler (injected).

Depends(handler_factory(ListTransactionsByAccountHandler))
date_handler ListTransactionsByDateRangeHandler

List by date range handler (injected).

Depends(handler_factory(ListTransactionsByDateRangeHandler))

Returns:

Type Description
TransactionListResponse | JSONResponse

TransactionListResponse with list of transactions.

TransactionListResponse | JSONResponse

JSONResponse with RFC 9457 error on failure.

Source code in src/presentation/routers/api/v1/transactions.py
async def list_transactions_by_account(
    request: Request,
    current_user: AuthenticatedUser,
    account_id: Annotated[UUID, Path(description="Account UUID")],
    limit: Annotated[
        int,
        Query(description="Maximum number of results", ge=1, le=100),
    ] = 50,
    offset: Annotated[
        int,
        Query(description="Number of results to skip", ge=0),
    ] = 0,
    transaction_type: Annotated[
        str | None,
        Query(description="Filter by transaction type (e.g., trade, transfer)"),
    ] = None,
    start_date: Annotated[
        date | None,
        Query(description="Filter by start date (inclusive)"),
    ] = None,
    end_date: Annotated[
        date | None,
        Query(description="Filter by end date (inclusive)"),
    ] = None,
    account_handler: ListTransactionsByAccountHandler = Depends(
        handler_factory(ListTransactionsByAccountHandler)
    ),
    date_handler: ListTransactionsByDateRangeHandler = Depends(
        handler_factory(ListTransactionsByDateRangeHandler)
    ),
) -> TransactionListResponse | JSONResponse:
    """List transactions for a specific account.

    GET /api/v1/accounts/{id}/transactions → 200 OK

    Supports pagination via limit/offset and filtering by:
    - transaction_type: Filter to specific type (e.g., "trade")
    - start_date/end_date: Date range filter

    Args:
        request: FastAPI request object.
        current_user: Authenticated user (from JWT).
        account_id: Account UUID.
        limit: Maximum results to return.
        offset: Number of results to skip.
        transaction_type: Optional type filter.
        start_date: Optional start date filter.
        end_date: Optional end date filter.
        account_handler: List by account handler (injected).
        date_handler: List by date range handler (injected).

    Returns:
        TransactionListResponse with list of transactions.
        JSONResponse with RFC 9457 error on failure.
    """
    # Use date range handler if dates provided, otherwise use account handler
    if start_date and end_date:
        date_query = ListTransactionsByDateRange(
            account_id=account_id,
            user_id=current_user.user_id,
            start_date=start_date,
            end_date=end_date,
        )
        result = await date_handler.handle(date_query)
    else:
        account_query = ListTransactionsByAccount(
            account_id=account_id,
            user_id=current_user.user_id,
            limit=limit,
            offset=offset,
            transaction_type=transaction_type,
        )
        result = await account_handler.handle(account_query)

    if isinstance(result, Failure):
        app_error = _map_transaction_error(result.error)
        return ErrorResponseBuilder.from_application_error(
            error=app_error,
            request=request,
            trace_id=get_trace_id() or "",
        )

    return TransactionListResponse.from_dto(result.value)