mirror of
https://github.com/logos-blockchain/logos-blockchain-block-explorer-template.git
synced 2026-04-10 04:43:08 +00:00
- Add search endpoint to backend (GET /api/v1/transactions/search) - Support search by transaction hash (partial match) or block height - Add search bar UI to TransactionsTable component - Increase default page size from 10 to 50 transactions - Add Block Height and Block Slot columns to transaction table - Debounce search input (300ms) for better UX Fixes: - Fix health endpoint JSON serialization - Fix main.py import path
107 lines
4.3 KiB
Python
107 lines
4.3 KiB
Python
from http.client import NOT_FOUND
|
|
from typing import TYPE_CHECKING, AsyncIterator, List
|
|
|
|
from fastapi import Query
|
|
from rusty_results import Empty, Option, Some
|
|
from starlette.responses import JSONResponse, Response
|
|
|
|
from api.streams import into_ndjson_stream
|
|
from api.v1.serializers.transactions import TransactionRead
|
|
from core.api import NBERequest, NDJsonStreamingResponse
|
|
from core.types import dehexify
|
|
from models.transactions.transaction import Transaction
|
|
|
|
if TYPE_CHECKING:
|
|
from core.app import NBE
|
|
|
|
|
|
async def _get_transactions_stream_serialized(
|
|
app: "NBE", transaction_from: Option[Transaction], *, fork: int
|
|
) -> AsyncIterator[List[TransactionRead]]:
|
|
_stream = app.state.transaction_repository.updates_stream(transaction_from, fork=fork)
|
|
async for transactions in _stream:
|
|
yield [TransactionRead.from_transaction(transaction) for transaction in transactions]
|
|
|
|
|
|
async def stream(
|
|
request: NBERequest,
|
|
prefetch_limit: int = Query(0, alias="prefetch-limit", ge=0),
|
|
fork: int = Query(...),
|
|
) -> Response:
|
|
latest_transactions: List[Transaction] = await request.app.state.transaction_repository.get_latest(
|
|
prefetch_limit, fork=fork, ascending=True, preload_relationships=True
|
|
)
|
|
latest_transaction = Some(latest_transactions[-1]) if latest_transactions else Empty()
|
|
bootstrap_transactions = [TransactionRead.from_transaction(transaction) for transaction in latest_transactions]
|
|
|
|
transactions_stream: AsyncIterator[List[TransactionRead]] = _get_transactions_stream_serialized(
|
|
request.app, latest_transaction, fork=fork
|
|
)
|
|
ndjson_transactions_stream = into_ndjson_stream(transactions_stream, bootstrap_data=bootstrap_transactions)
|
|
return NDJsonStreamingResponse(ndjson_transactions_stream)
|
|
|
|
|
|
async def list_transactions(
|
|
request: NBERequest,
|
|
page: int = Query(0, ge=0),
|
|
page_size: int = Query(10, ge=1, le=100, alias="page-size"),
|
|
fork: int = Query(...),
|
|
) -> Response:
|
|
transactions, total_count = await request.app.state.transaction_repository.get_paginated(page, page_size, fork=fork)
|
|
total_pages = (total_count + page_size - 1) // page_size
|
|
|
|
return JSONResponse(
|
|
{
|
|
"transactions": [TransactionRead.from_transaction(tx).model_dump(mode="json") for tx in transactions],
|
|
"page": page,
|
|
"page_size": page_size,
|
|
"total_count": total_count,
|
|
"total_pages": total_pages,
|
|
}
|
|
)
|
|
|
|
|
|
async def get(request: NBERequest, transaction_hash: str, fork: int = Query(...)) -> Response:
|
|
if not transaction_hash:
|
|
return Response(status_code=NOT_FOUND)
|
|
transaction_hash = dehexify(transaction_hash)
|
|
transaction = await request.app.state.transaction_repository.get_by_hash(transaction_hash, fork=fork)
|
|
return transaction.map(
|
|
lambda _transaction: JSONResponse(TransactionRead.from_transaction(_transaction).model_dump(mode="json"))
|
|
).unwrap_or_else(lambda: Response(status_code=NOT_FOUND))
|
|
|
|
|
|
async def search(
|
|
request: NBERequest,
|
|
q: str = Query(..., description="Search query (hash partial match or block height)"),
|
|
page: int = Query(0, ge=0, description="Page number"),
|
|
page_size: int = Query(50, ge=1, le=100, description="Items per page"),
|
|
fork: int = Query(..., description="Fork ID"),
|
|
) -> Response:
|
|
"""Search transactions by hash or block height."""
|
|
if not q:
|
|
return JSONResponse({"transactions": [], "page": page, "page_size": page_size, "total_count": 0, "total_pages": 0})
|
|
|
|
# Try to parse as block height (integer)
|
|
try:
|
|
block_height = int(q)
|
|
transactions, total_count = await request.app.state.transaction_repository.search_by_block_height(
|
|
block_height, fork=fork, page=page, page_size=page_size
|
|
)
|
|
except ValueError:
|
|
# Search by hash (partial match)
|
|
transactions, total_count = await request.app.state.transaction_repository.search(
|
|
q, fork=fork, page=page, page_size=page_size
|
|
)
|
|
|
|
total_pages = (total_count + page_size - 1) // page_size if total_count > 0 else 0
|
|
|
|
return JSONResponse({
|
|
"transactions": [TransactionRead.from_transaction(tx).model_dump(mode="json") for tx in transactions],
|
|
"page": page,
|
|
"page_size": page_size,
|
|
"total_count": total_count,
|
|
"total_pages": total_pages,
|
|
"query": q,
|
|
})
|