diff --git a/internal/middleware/ratelimiter.go b/internal/middleware/ratelimiter.go index f8d3c52..1f9ad61 100644 --- a/internal/middleware/ratelimiter.go +++ b/internal/middleware/ratelimiter.go @@ -111,8 +111,7 @@ func calculateNextCleanupTime(ctx context.Context, client *db.DiceDB, cronFreque lastCleanupTime := time.UnixMilli(lastCronCleanupTime) nextCleanupTime := lastCleanupTime.Add(cronFrequencyInterval) - timeDifference := nextCleanupTime.Sub(time.Now()) - return int64(timeDifference.Seconds()), nil + return int64(time.Until(nextCleanupTime).Seconds()), nil } func MockRateLimiter(client *mock.DiceDBMock, next http.Handler, limit int64, window float64) http.Handler { diff --git a/internal/server/http.go b/internal/server/http.go index b5209c2..cd4f631 100644 --- a/internal/server/http.go +++ b/internal/server/http.go @@ -4,14 +4,20 @@ import ( "context" "encoding/json" "errors" + "fmt" "log/slog" "net/http" + "strconv" "strings" "time" + "server/config" "server/internal/db" "server/internal/middleware" + "server/internal/server/utils" util "server/util" + + "github.com/dicedb/dicedb-go" ) type HTTPServer struct { @@ -100,6 +106,22 @@ func (s *HTTPServer) Shutdown() error { } func (s *HTTPServer) HealthCheck(w http.ResponseWriter, request *http.Request) { + nextCleanup, err := s.getNextCleanupTime() + if err != nil { + slog.Error("Failed to get the cleanupTime", slog.Any("err", err)) + http.Error(w, errorResponse("internal server error"), http.StatusInternalServerError) + return + } + + commandsLeft, err := s.getCommandsLeft() + if err != nil { + slog.Error("Failed to get the commands Left", slog.Any("err", err)) + http.Error(w, errorResponse("internal server error"), http.StatusInternalServerError) + return + } + + w.Header().Set("x-next-cleanup-time", strconv.FormatInt(nextCleanup, 10)) + w.Header().Set("x-commands-left", strconv.FormatInt(commandsLeft, 10)) util.JSONResponse(w, http.StatusOK, map[string]string{"message": "server is running"}) } @@ -142,3 +164,52 @@ func (s *HTTPServer) CliHandler(w http.ResponseWriter, r *http.Request) { func (s *HTTPServer) SearchHandler(w http.ResponseWriter, request *http.Request) { util.JSONResponse(w, http.StatusOK, map[string]string{"message": "search results"}) } + +func (s *HTTPServer) getNextCleanupTime() (int64, error) { + resp := s.DiceClient.Client.Get(context.Background(), utils.LastCronCleanupTimeUnixMs) + if resp.Err() != nil { + if errors.Is(resp.Err(), dicedb.Nil) { + return time.Now().UnixMilli(), nil + } + return 0, resp.Err() + } + + lastCleanupStr := resp.Val() + if lastCleanupStr == "" { + return 0, resp.Err() + } + + lastCleanup, err := strconv.ParseInt(lastCleanupStr, 10, 64) + if err != nil { + return 0, err + } + lastCleanupTime := time.UnixMilli(lastCleanup) + nextCleanupTime := lastCleanupTime.Add(15 * time.Minute).UnixMilli() + + return nextCleanupTime, nil +} + +func (s *HTTPServer) getCommandsLeft() (int64, error) { + configValue := config.LoadConfig() + currentWindow := time.Now().Unix() / int64(configValue.Server.RequestWindowSec) + key := fmt.Sprintf("request_count:%d", currentWindow) + + val, err := s.DiceClient.Client.Get(context.Background(), key).Result() + if err != nil { + if errors.Is(err, dicedb.Nil) { + return 1000, nil + } + return 0, err + } + + requestCount, err := strconv.ParseInt(val, 10, 64) + if err != nil { + return 0, err + } + + remaining := int64(1000) - requestCount + if remaining < 0 { + remaining = 0 + } + return remaining, nil +}