Add full monorepo: virtual-banker, backend, frontend, docs, scripts, deployment
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
167
backend/api/track3/endpoints.go
Normal file
167
backend/api/track3/endpoints.go
Normal file
@@ -0,0 +1,167 @@
|
||||
package track3
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/explorer/backend/analytics"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
// Server handles Track 3 endpoints
|
||||
type Server struct {
|
||||
db *pgxpool.Pool
|
||||
flowTracker *analytics.FlowTracker
|
||||
bridgeAnalytics *analytics.BridgeAnalytics
|
||||
tokenDist *analytics.TokenDistribution
|
||||
riskAnalyzer *analytics.AddressRiskAnalyzer
|
||||
chainID int
|
||||
}
|
||||
|
||||
// NewServer creates a new Track 3 server
|
||||
func NewServer(db *pgxpool.Pool, chainID int) *Server {
|
||||
return &Server{
|
||||
db: db,
|
||||
flowTracker: analytics.NewFlowTracker(db, chainID),
|
||||
bridgeAnalytics: analytics.NewBridgeAnalytics(db),
|
||||
tokenDist: analytics.NewTokenDistribution(db, chainID),
|
||||
riskAnalyzer: analytics.NewAddressRiskAnalyzer(db, chainID),
|
||||
chainID: chainID,
|
||||
}
|
||||
}
|
||||
|
||||
// HandleFlows handles GET /api/v1/track3/analytics/flows
|
||||
func (s *Server) HandleFlows(w http.ResponseWriter, r *http.Request) {
|
||||
from := r.URL.Query().Get("from")
|
||||
to := r.URL.Query().Get("to")
|
||||
token := r.URL.Query().Get("token")
|
||||
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
|
||||
if limit < 1 || limit > 200 {
|
||||
limit = 50
|
||||
}
|
||||
|
||||
var startDate, endDate *time.Time
|
||||
if startStr := r.URL.Query().Get("start_date"); startStr != "" {
|
||||
if t, err := time.Parse(time.RFC3339, startStr); err == nil {
|
||||
startDate = &t
|
||||
}
|
||||
}
|
||||
if endStr := r.URL.Query().Get("end_date"); endStr != "" {
|
||||
if t, err := time.Parse(time.RFC3339, endStr); err == nil {
|
||||
endDate = &t
|
||||
}
|
||||
}
|
||||
|
||||
flows, err := s.flowTracker.GetFlows(r.Context(), from, to, token, startDate, endDate, limit)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "database_error", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
response := map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"flows": flows,
|
||||
},
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
// HandleBridge handles GET /api/v1/track3/analytics/bridge
|
||||
func (s *Server) HandleBridge(w http.ResponseWriter, r *http.Request) {
|
||||
var chainFrom, chainTo *int
|
||||
if cf := r.URL.Query().Get("chain_from"); cf != "" {
|
||||
if c, err := strconv.Atoi(cf); err == nil {
|
||||
chainFrom = &c
|
||||
}
|
||||
}
|
||||
if ct := r.URL.Query().Get("chain_to"); ct != "" {
|
||||
if c, err := strconv.Atoi(ct); err == nil {
|
||||
chainTo = &c
|
||||
}
|
||||
}
|
||||
|
||||
var startDate, endDate *time.Time
|
||||
if startStr := r.URL.Query().Get("start_date"); startStr != "" {
|
||||
if t, err := time.Parse(time.RFC3339, startStr); err == nil {
|
||||
startDate = &t
|
||||
}
|
||||
}
|
||||
if endStr := r.URL.Query().Get("end_date"); endStr != "" {
|
||||
if t, err := time.Parse(time.RFC3339, endStr); err == nil {
|
||||
endDate = &t
|
||||
}
|
||||
}
|
||||
|
||||
stats, err := s.bridgeAnalytics.GetBridgeStats(r.Context(), chainFrom, chainTo, startDate, endDate)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "database_error", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
response := map[string]interface{}{
|
||||
"data": stats,
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
// HandleTokenDistribution handles GET /api/v1/track3/analytics/token-distribution
|
||||
func (s *Server) HandleTokenDistribution(w http.ResponseWriter, r *http.Request) {
|
||||
path := strings.TrimPrefix(r.URL.Path, "/api/v1/track3/analytics/token-distribution/")
|
||||
contract := strings.ToLower(path)
|
||||
|
||||
topN, _ := strconv.Atoi(r.URL.Query().Get("top_n"))
|
||||
if topN < 1 || topN > 1000 {
|
||||
topN = 100
|
||||
}
|
||||
|
||||
stats, err := s.tokenDist.GetTokenDistribution(r.Context(), contract, topN)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusNotFound, "not_found", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
response := map[string]interface{}{
|
||||
"data": stats,
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
// HandleAddressRisk handles GET /api/v1/track3/analytics/address-risk/:addr
|
||||
func (s *Server) HandleAddressRisk(w http.ResponseWriter, r *http.Request) {
|
||||
path := strings.TrimPrefix(r.URL.Path, "/api/v1/track3/analytics/address-risk/")
|
||||
address := strings.ToLower(path)
|
||||
|
||||
analysis, err := s.riskAnalyzer.AnalyzeAddress(r.Context(), address)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "database_error", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
response := map[string]interface{}{
|
||||
"data": analysis,
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
func writeError(w http.ResponseWriter, statusCode int, code, message string) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(statusCode)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"error": map[string]interface{}{
|
||||
"code": code,
|
||||
"message": message,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user