Add full monorepo: virtual-banker, backend, frontend, docs, scripts, deployment
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
35
backend/search/config/search.go
Normal file
35
backend/search/config/search.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// SearchConfig holds Elasticsearch/OpenSearch configuration
|
||||
type SearchConfig struct {
|
||||
URL string
|
||||
Username string
|
||||
Password string
|
||||
UseSSL bool
|
||||
IndexPrefix string
|
||||
}
|
||||
|
||||
// LoadSearchConfig loads search configuration from environment variables
|
||||
func LoadSearchConfig() *SearchConfig {
|
||||
useSSL, _ := strconv.ParseBool(getEnv("SEARCH_USE_SSL", "false"))
|
||||
|
||||
return &SearchConfig{
|
||||
URL: getEnv("SEARCH_URL", "http://localhost:9200"),
|
||||
Username: getEnv("SEARCH_USERNAME", ""),
|
||||
Password: getEnv("SEARCH_PASSWORD", ""),
|
||||
UseSSL: useSSL,
|
||||
IndexPrefix: getEnv("SEARCH_INDEX_PREFIX", "explorer"),
|
||||
}
|
||||
}
|
||||
|
||||
func getEnv(key, defaultValue string) string {
|
||||
if value := os.Getenv(key); value != "" {
|
||||
return value
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
120
backend/search/indexer/indexer.go
Normal file
120
backend/search/indexer/indexer.go
Normal file
@@ -0,0 +1,120 @@
|
||||
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"`
|
||||
}
|
||||
Reference in New Issue
Block a user