Files
explorer-monorepo/backend/search/indexer/indexer.go

121 lines
4.1 KiB
Go

package indexer
import (
"bytes"
"context"
"encoding/json"
"fmt"
"time"
"github.com/elastic/go-elasticsearch/v8"
"github.com/elastic/go-elasticsearch/v8/esapi"
)
// SearchIndexer handles indexing documents to Elasticsearch/OpenSearch
type SearchIndexer struct {
client *elasticsearch.Client
indexPrefix string
}
// NewSearchIndexer creates a new search indexer
func NewSearchIndexer(client *elasticsearch.Client, indexPrefix string) *SearchIndexer {
return &SearchIndexer{
client: client,
indexPrefix: indexPrefix,
}
}
// IndexBlock indexes a block document
func (s *SearchIndexer) IndexBlock(ctx context.Context, chainID int, block *BlockDocument) error {
indexName := fmt.Sprintf("%s-blocks-%d", s.indexPrefix, chainID)
return s.indexDocument(ctx, indexName, block.Hash, block)
}
// IndexTransaction indexes a transaction document
func (s *SearchIndexer) IndexTransaction(ctx context.Context, chainID int, tx *TransactionDocument) error {
indexName := fmt.Sprintf("%s-transactions-%d", s.indexPrefix, chainID)
return s.indexDocument(ctx, indexName, tx.Hash, tx)
}
// IndexAddress indexes an address document
func (s *SearchIndexer) IndexAddress(ctx context.Context, chainID int, addr *AddressDocument) error {
indexName := fmt.Sprintf("%s-addresses-%d", s.indexPrefix, chainID)
return s.indexDocument(ctx, indexName, addr.Address, addr)
}
// indexDocument indexes a document to Elasticsearch
func (s *SearchIndexer) indexDocument(ctx context.Context, indexName, docID string, doc interface{}) error {
body, err := json.Marshal(doc)
if err != nil {
return fmt.Errorf("failed to marshal document: %w", err)
}
req := esapi.IndexRequest{
Index: indexName,
DocumentID: docID,
Body: bytes.NewReader(body),
Refresh: "false",
}
res, err := req.Do(ctx, s.client)
if err != nil {
return fmt.Errorf("failed to index document: %w", err)
}
defer res.Body.Close()
if res.IsError() {
return fmt.Errorf("elasticsearch error: %s", res.String())
}
return nil
}
// BlockDocument represents a block in the search index
type BlockDocument struct {
BlockNumber int64 `json:"block_number"`
Hash string `json:"hash"`
Timestamp time.Time `json:"timestamp"`
Miner string `json:"miner"`
TransactionCount int `json:"transaction_count"`
GasUsed int64 `json:"gas_used"`
GasLimit int64 `json:"gas_limit"`
ChainID int `json:"chain_id"`
ParentHash string `json:"parent_hash"`
Size int64 `json:"size"`
}
// TransactionDocument represents a transaction in the search index
type TransactionDocument struct {
Hash string `json:"hash"`
BlockNumber int64 `json:"block_number"`
TransactionIndex int `json:"transaction_index"`
FromAddress string `json:"from_address"`
ToAddress string `json:"to_address"`
Value string `json:"value"`
ValueNumeric int64 `json:"value_numeric"`
GasPrice int64 `json:"gas_price"`
GasUsed int64 `json:"gas_used"`
Status string `json:"status"`
Timestamp time.Time `json:"timestamp"`
ChainID int `json:"chain_id"`
InputDataLength int `json:"input_data_length"`
IsContractCreation bool `json:"is_contract_creation"`
ContractAddress string `json:"contract_address,omitempty"`
}
// AddressDocument represents an address in the search index
type AddressDocument struct {
Address string `json:"address"`
ChainID int `json:"chain_id"`
Label string `json:"label,omitempty"`
Tags []string `json:"tags,omitempty"`
TokenCount int `json:"token_count"`
TransactionCount int64 `json:"transaction_count"`
FirstSeen time.Time `json:"first_seen"`
LastSeen time.Time `json:"last_seen"`
IsContract bool `json:"is_contract"`
ContractName string `json:"contract_name,omitempty"`
BalanceETH string `json:"balance_eth"`
BalanceUSD float64 `json:"balance_usd,omitempty"`
}