diff --git a/api/latency.go b/api/latency.go index 99147c5..aa2d0ea 100644 --- a/api/latency.go +++ b/api/latency.go @@ -74,6 +74,10 @@ func (l *LatencyQueue) SetHandler(handler chan<- PingResult) { l.handlerChannel = handler } +func (l *LatencyQueue) GetSelfIP() net.IP { + return l.ipSelf +} + func (l *LatencyQueue) RefreshIP() error { resp, err := http.Get("https://api.ipify.org?format=text") if err != nil { @@ -145,8 +149,6 @@ func (l *LatencyQueue) Start(ctx context.Context) { pinger.Interval = time.Nanosecond pinger.Timeout = time.Millisecond * 500 - l.logger.Debugw("Ping Request", "ip", ip) - // Process the request err = pinger.Run() if err != nil { diff --git a/api/score.go b/api/score.go index 08c295e..e694f35 100644 --- a/api/score.go +++ b/api/score.go @@ -3,6 +3,7 @@ package api import ( "fmt" "math" + "slices" "go.uber.org/zap" ) @@ -190,10 +191,21 @@ func ScoreOffers(offers []Offer) []ScoredOffer { } } newScore := score * multiplier - // sugar.Infow("Multiplier Applied", "offer", offer.ID, "baseScore", score, "score", newScore, "multiplier", multiplier) score = newScore scoredOffers = append(scoredOffers, ScoredOffer{Offer: offer, Score: score, Reasons: reasons}) } + + // Sort by score + slices.SortStableFunc(scoredOffers, func(a, b ScoredOffer) int { + if a.Score < b.Score { + return 1 + } else if a.Score > b.Score { + return -1 + } else { + return 0 + } + }) + return scoredOffers } diff --git a/app.go b/app.go index fc127c4..c9c0acd 100644 --- a/app.go +++ b/app.go @@ -2,7 +2,9 @@ package main import ( "context" + "fmt" "os" + "time" "github.com/joho/godotenv" "github.com/redis/go-redis/v9" @@ -16,6 +18,7 @@ type App struct { client *api.Client logger *zap.SugaredLogger latency *api.LatencyQueue + redis *redis.Client } // NewApp creates a new App application struct @@ -51,10 +54,14 @@ func (a *App) startup(ctx context.Context) { if err != nil { a.logger.Fatal("Failed to parse Redis URL", err) } - redis := redis.NewClient(redisOptions) + + a.redis = redis.NewClient(redisOptions) + if _, err := a.redis.Ping(ctx).Result(); err != nil { + a.logger.Fatal("Failed to connect to Redis", err) + } // Start latency queue - a.latency = api.NewLatencyQueue(redis) + a.latency = api.NewLatencyQueue(a.redis) go a.latency.Start(ctx) } @@ -82,5 +89,36 @@ func (a *App) Search() []api.ScoredOffer { a.latency.QueuePing(offer.PublicIPAddr) } - return api.ScoreOffers(resp.Offers) + scored := api.ScoreOffers(resp.Offers) + + // collect IP latency keys + currentIP := a.latency.GetSelfIP() + batchCount := min(100, len(scored)) + latencyKeys := make([]string, batchCount, batchCount) + for i := range batchCount { + latencyKeys[i] = fmt.Sprintf("latency:%s:%s", currentIP, scored[i].Offer.PublicIPAddr) + } + latencyValues, err := a.redis.MGet(a.ctx, latencyKeys...).Result() + if err != nil { + a.logger.Errorw("Failed to get latency values", "error", err) + } + + // Assign latency values to scored offers + for i, value := range latencyValues { + if value != nil { + if value.(string) == "timeout" { + scored[i].Latency = api.Pointer(int32(-1)) + continue + } + + parsed, err := time.ParseDuration(value.(string)) + if err != nil { + a.logger.Errorw("Failed to parse latency value", "error", err) + continue + } + scored[i].Latency = api.Pointer(int32(parsed.Milliseconds())) + } + } + + return scored } diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 4624822..926248c 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -8,8 +8,6 @@ function App() { async function invoke() { const offers = await Search(); - console.log({ offer: offers[0] }); - offers.sort((a, b) => b.Score - a.Score); setState(offers); }