From 563b77b85ad037bb65b01737800481384953ed34 Mon Sep 17 00:00:00 2001 From: Swarit Pandey Date: Sun, 29 Sep 2024 09:50:18 +0000 Subject: [PATCH 1/3] chore: refactor server code Signed-off-by: Swarit Pandey --- config/config.go | 2 +- internal/db/commands.go | 16 --- internal/db/dicedb.go | 102 ----------------- internal/handlers/handlers.go | 73 ++++++++++++ internal/middleware/ratelimiter.go | 9 +- internal/repository/repository.go | 33 ++++++ internal/repository/request.go | 14 +++ internal/server/http.go | 123 ++++++++++++++++++++ internal/server/httpServer.go | 133 ---------------------- internal/service/cli.go | 53 +++++++++ internal/service/health.go | 3 + internal/service/search.go | 3 + internal/service/types.go | 57 ++++++++++ main.go | 29 +---- pkg/{util/helpers.go => common/common.go} | 0 15 files changed, 370 insertions(+), 280 deletions(-) delete mode 100644 internal/db/commands.go delete mode 100644 internal/db/dicedb.go create mode 100644 internal/handlers/handlers.go create mode 100644 internal/repository/repository.go create mode 100644 internal/repository/request.go create mode 100644 internal/server/http.go delete mode 100644 internal/server/httpServer.go create mode 100644 internal/service/cli.go create mode 100644 internal/service/health.go create mode 100644 internal/service/search.go create mode 100644 internal/service/types.go rename pkg/{util/helpers.go => common/common.go} (100%) diff --git a/config/config.go b/config/config.go index a9991de..0bc5630 100644 --- a/config/config.go +++ b/config/config.go @@ -14,7 +14,7 @@ type Config struct { } // LoadConfig loads the application configuration from environment variables or defaults -func LoadConfig() *Config { +func Load() *Config { return &Config{ DiceAddr: getEnv("DICE_ADDR", "localhost:7379"), // Default Dice address ServerPort: getEnv("SERVER_PORT", ":8080"), // Default server port diff --git a/internal/db/commands.go b/internal/db/commands.go deleted file mode 100644 index f78b616..0000000 --- a/internal/db/commands.go +++ /dev/null @@ -1,16 +0,0 @@ -package db - -func (db *DiceDB) getKey(key string) (string, error) { - val, err := db.Client.Get(db.Ctx, key).Result() - return val, err -} - -func (db *DiceDB) setKey(key, value string) error { - err := db.Client.Set(db.Ctx, key, value, 0).Err() - return err -} - -func (db *DiceDB) deleteKeys(keys []string) error { - err := db.Client.Del(db.Ctx, keys...).Err() - return err -} diff --git a/internal/db/dicedb.go b/internal/db/dicedb.go deleted file mode 100644 index c811aac..0000000 --- a/internal/db/dicedb.go +++ /dev/null @@ -1,102 +0,0 @@ -/* -this will be the DiceDB client -*/ - -package db - -import ( - "context" - "errors" - "fmt" - "log/slog" - "os" - "server/config" - "server/internal/cmds" - "time" - - dice "github.com/dicedb/go-dice" -) - -const ( - RespOK = "OK" -) - -type DiceDB struct { - Client *dice.Client - Ctx context.Context -} - -func (db *DiceDB) CloseDiceDB() { - err := db.Client.Close() - if err != nil { - slog.Error("error closing DiceDB connection", - slog.Any("error", err)) - os.Exit(1) - } -} - -func InitDiceClient(configValue *config.Config) (*DiceDB, error) { - diceClient := dice.NewClient(&dice.Options{ - Addr: configValue.DiceAddr, - DialTimeout: 10 * time.Second, - MaxRetries: 10, - }) - - // Ping the dice client to verify the connection - err := diceClient.Ping(context.Background()).Err() - if err != nil { - return nil, err - } - - return &DiceDB{ - Client: diceClient, - Ctx: context.Background(), - }, nil -} - -// ExecuteCommand executes a command based on the input -func (db *DiceDB) ExecuteCommand(command *cmds.CommandRequest) (interface{}, error) { - switch command.Cmd { - case "GET": - if len(command.Args) != 1 { - return nil, errors.New("invalid args") - } - - val, err := db.getKey(command.Args[0]) - switch { - case errors.Is(err, dice.Nil): - return nil, errors.New("key does not exist") - case err != nil: - return nil, fmt.Errorf("get failed %v", err) - } - - return val, nil - - case "SET": - if len(command.Args) < 2 { - return nil, errors.New("key is required") - } - - err := db.setKey(command.Args[0], command.Args[1]) - if err != nil { - return nil, errors.New("failed to set key") - } - - return RespOK, nil - - case "DEL": - if len(command.Args) == 0 { - return nil, errors.New("at least one key is required") - } - - err := db.deleteKeys(command.Args) - if err != nil { - return nil, errors.New("failed to delete keys") - } - - return RespOK, nil - - default: - return nil, errors.New("unknown command") - } -} diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go new file mode 100644 index 0000000..e9746f0 --- /dev/null +++ b/internal/handlers/handlers.go @@ -0,0 +1,73 @@ +package handlers + +import ( + "net/http" + "server/internal/service" + "server/pkg/common" +) + +const ( + OpGET = "get" + OpSET = "set" + OpDEL = "delete" +) + +type Handler interface { + CLIHandler(w http.ResponseWriter, r *http.Request) + Health(w http.ResponseWriter, r *http.Request) + Search(w http.ResponseWriter, r *http.Request) +} + +type handler struct { + service service.Service +} + +func NewHandler(service service.Service) Handler { + return &handler{service: service} +} + +func (h *handler) CLIHandler(w http.ResponseWriter, r *http.Request) { + command, err := common.ParseHTTPRequest(r) + if err != nil { + common.JSONResponse(w, http.StatusBadRequest, map[string]string{"error": err.Error()}) + } + + var result any + switch command.Cmd { + + case OpGET: + result, err = h.service.Get(r.Context(), &service.GetRequest{Key: command.Args.Key}) + + case OpSET: + result, err = h.service.Set(r.Context(), &service.SetRequest{Key: command.Args.Key, Value: command.Args.Value}) + + case OpDEL: + var keys []string + if command.Args.Key != "" { + keys = []string{command.Args.Key} + } else if len(command.Args.Keys) > 0 { + keys = command.Args.Keys + } else { + common.JSONResponse(w, http.StatusBadRequest, map[string]string{"error": "at least one key is required"}) + return + } + result, err = h.service.Delete(r.Context(), &service.DeleteRequest{Keys: keys}) + } + + if err != nil { + common.JSONResponse(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + return + } + + common.JSONResponse(w, http.StatusOK, result) +} + +func (h *handler) Health(w http.ResponseWriter, r *http.Request) { + // TODO: Call service layer for Health check + common.JSONResponse(w, http.StatusOK, map[string]string{"message": "Server is running"}) +} + +func (h *handler) Search(w http.ResponseWriter, r *http.Request) { + // TODO: Call service layer for search over keys or whatever + common.JSONResponse(w, http.StatusOK, map[string]string{"message": "Results..."}) +} diff --git a/internal/middleware/ratelimiter.go b/internal/middleware/ratelimiter.go index 293dd9c..52ad703 100644 --- a/internal/middleware/ratelimiter.go +++ b/internal/middleware/ratelimiter.go @@ -6,7 +6,6 @@ import ( "fmt" "log/slog" "net/http" - "server/internal/db" "strconv" "strings" "time" @@ -22,7 +21,7 @@ func enableCors(w http.ResponseWriter) { } // RateLimiter middleware to limit requests based on a specified limit and duration -func RateLimiter(client *db.DiceDB, next http.Handler, limit, window int) http.Handler { +func RateLimiter(client *dice.Client, next http.Handler, limit, window int) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() @@ -48,7 +47,7 @@ func RateLimiter(client *db.DiceDB, next http.Handler, limit, window int) http.H slog.Info("Created rate limiter key", slog.Any("key", key)) // Fetch the current request count - val, err := client.Client.Get(ctx, key).Result() + val, err := client.Get(ctx, key).Result() if err != nil && !errors.Is(err, dice.Nil) { slog.Error("Error fetching request count", "error", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) @@ -74,7 +73,7 @@ func RateLimiter(client *db.DiceDB, next http.Handler, limit, window int) http.H } // Increment the request count - if _, err := client.Client.Incr(ctx, key).Result(); err != nil { + if _, err := client.Incr(ctx, key).Result(); err != nil { slog.Error("Error incrementing request count", "error", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return @@ -82,7 +81,7 @@ func RateLimiter(client *db.DiceDB, next http.Handler, limit, window int) http.H // Set the key expiry if it's newly created if requestCount == 0 { - if err := client.Client.Expire(ctx, key, time.Duration(window)*time.Second).Err(); err != nil { + if err := client.Expire(ctx, key, time.Duration(window)*time.Second).Err(); err != nil { slog.Error("Error setting expiry for request count", "error", err) } } diff --git a/internal/repository/repository.go b/internal/repository/repository.go new file mode 100644 index 0000000..c2a01d0 --- /dev/null +++ b/internal/repository/repository.go @@ -0,0 +1,33 @@ +package repository + +import ( + "context" + + dice "github.com/dicedb/go-dice" +) + +type Repository interface { + Get(ctx context.Context, req *GetRequest) (string, error) + Set(ctx context.Context, req *SetRequest) error + Delete(ctx context.Context, req *DeleteRequest) error +} + +type repository struct { + client *dice.Client +} + +func NewRepository(client *dice.Client) Repository { + return &repository{client: client} +} + +func (r *repository) Get(ctx context.Context, req *GetRequest) (string, error) { + return r.client.Get(ctx, req.Key).Result() +} + +func (r *repository) Set(ctx context.Context, req *SetRequest) error { + return r.client.Set(ctx, req.Key, req.Value, 0).Err() +} + +func (r *repository) Delete(ctx context.Context, req *DeleteRequest) error { + return r.client.Del(ctx, req.Keys...).Err() +} diff --git a/internal/repository/request.go b/internal/repository/request.go new file mode 100644 index 0000000..e2f0aec --- /dev/null +++ b/internal/repository/request.go @@ -0,0 +1,14 @@ +package repository + +type GetRequest struct { + Key string +} + +type SetRequest struct { + Key string + Value string +} + +type DeleteRequest struct { + Keys []string +} diff --git a/internal/server/http.go b/internal/server/http.go new file mode 100644 index 0000000..b1d04d3 --- /dev/null +++ b/internal/server/http.go @@ -0,0 +1,123 @@ +package server + +import ( + "context" + "fmt" + "log" + "net/http" + "os" + "os/signal" + "strings" + "syscall" + "time" + + "server/config" + "server/internal/handlers" + "server/internal/middleware" + "server/internal/repository" + "server/internal/service" + + dice "github.com/dicedb/go-dice" +) + +type Server struct { + httpServer *http.Server + config *config.Config +} + +func NewServer(config *config.Config) *Server { + return &Server{ + config: config, + } +} + +func (s *Server) Run() error { + // Step1: Initialize DiceDB client + diceClient, err := initDiceClient(s.config) + if err != nil { + return fmt.Errorf("failed to initialize DiceDB client: %w", err) + } + + // Step2: Initialize repository + repo := repository.NewRepository(diceClient) + + // Step3: Initialize service + svc := service.NewService(repo) + + // Step4: Initialize handler + handler := handlers.NewHandler(svc) + + // TODO: We should ideally move this to routes package + // Step5: Set up routes + mux := http.NewServeMux() + mux.HandleFunc("/health", handler.Health) + mux.HandleFunc("/cli/{cmd}", handler.CLIHandler) + mux.HandleFunc("/search", handler.Search) + + handlerMux := &HandlerMux{ + mux: mux, + rateLimiter: func(w http.ResponseWriter, r *http.Request, next http.Handler) { + middleware.RateLimiter(diceClient, next, s.config.RequestLimit, s.config.RequestWindow).ServeHTTP(w, r) + }, + } + + // Create HTTP server + s.httpServer = &http.Server{ + Addr: s.config.ServerPort, + Handler: handlerMux, + } + + // Start server + go func() { + log.Printf("Starting server on %s", s.config.ServerPort) + if err := s.httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Fatalf("Server failed to start: %v", err) + } + }() + + quit := make(chan os.Signal, 1) + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) + <-quit + log.Println("Shutting down server...") + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + // Doesn't block if no connections, but will otherwise wait until the timeout deadline + if err := s.httpServer.Shutdown(ctx); err != nil { + return fmt.Errorf("server forced to shutdown: %w", err) + } + + log.Println("Server exited properly") + return nil +} + +// HandlerMux wraps ServeMux and forces REST paths to lowercase +// and attaches a rate limiter with the handler +type HandlerMux struct { + mux *http.ServeMux + rateLimiter func(http.ResponseWriter, *http.Request, http.Handler) +} + +func (hm *HandlerMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // Convert the path to lowercase before passing to the underlying mux. + r.URL.Path = strings.ToLower(r.URL.Path) + hm.rateLimiter(w, r, hm.mux) +} + +// initDiceClient initializes dice client +func initDiceClient(config *config.Config) (*dice.Client, error) { + diceClient := dice.NewClient(&dice.Options{ + Addr: config.DiceAddr, + DialTimeout: 10 * time.Second, + MaxRetries: 10, + }) + + // Ping the dice client to verify the connection + err := diceClient.Ping(context.Background()).Err() + if err != nil { + return nil, err + } + + return diceClient, nil +} diff --git a/internal/server/httpServer.go b/internal/server/httpServer.go deleted file mode 100644 index 68e4c86..0000000 --- a/internal/server/httpServer.go +++ /dev/null @@ -1,133 +0,0 @@ -package server - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "log" - "net/http" - "server/internal/middleware" - "strings" - "sync" - "time" - - "server/internal/db" - util "server/pkg/util" -) - -type HTTPServer struct { - httpServer *http.Server - DiceClient *db.DiceDB -} - -// HandlerMux wraps ServeMux and forces REST paths to lowercase -// and attaches a rate limiter with the handler -type HandlerMux struct { - mux *http.ServeMux - rateLimiter func(http.ResponseWriter, *http.Request, http.Handler) -} - -type HTTPResponse struct { - Data interface{} `json:"data"` -} - -type HTTPErrorResponse struct { - Error interface{} `json:"error"` -} - -func errorResponse(response string) string { - return fmt.Sprintf("{\"error\": %q}", response) -} - -func (cim *HandlerMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { - // Convert the path to lowercase before passing to the underlying mux. - r.URL.Path = strings.ToLower(r.URL.Path) - // Apply rate limiter - cim.rateLimiter(w, r, cim.mux) -} - -func NewHTTPServer(addr string, mux *http.ServeMux, client *db.DiceDB, limit, window int) *HTTPServer { - handlerMux := &HandlerMux{ - mux: mux, - rateLimiter: func(w http.ResponseWriter, r *http.Request, next http.Handler) { - middleware.RateLimiter(client, next, limit, window).ServeHTTP(w, r) - }, - } - - return &HTTPServer{ - httpServer: &http.Server{ - Addr: addr, - Handler: handlerMux, - ReadHeaderTimeout: 5 * time.Second, - }, - DiceClient: client, - } -} - -func (s *HTTPServer) Run(ctx context.Context) error { - var wg sync.WaitGroup - - wg.Add(1) - go func() { - defer wg.Done() - log.Printf("Starting server at %s\n", s.httpServer.Addr) - if err := s.httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { - log.Fatalf("HTTP server error: %v", err) - } - }() - - <-ctx.Done() - log.Println("Shutting down server...") - return s.Shutdown() -} - -func (s *HTTPServer) Shutdown() error { - if err := s.DiceClient.Client.Close(); err != nil { - log.Printf("Failed to close dice client: %v", err) - } - - return s.httpServer.Shutdown(context.Background()) -} - -func (s *HTTPServer) HealthCheck(w http.ResponseWriter, request *http.Request) { - util.JSONResponse(w, http.StatusOK, map[string]string{"message": "Server is running"}) -} - -func (s *HTTPServer) CliHandler(w http.ResponseWriter, r *http.Request) { - diceCmd, err := util.ParseHTTPRequest(r) - if err != nil { - http.Error(w, "Error parsing HTTP request", http.StatusBadRequest) - return - } - - resp, err := s.DiceClient.ExecuteCommand(diceCmd) - if err != nil { - http.Error(w, errorResponse(err.Error()), http.StatusBadRequest) - return - } - - if _, ok := resp.(string); !ok { - log.Println("Error marshaling response", "error", err) - http.Error(w, errorResponse("Internal Server Error"), http.StatusInternalServerError) - return - } - - httpResponse := HTTPResponse{Data: resp.(string)} - responseJSON, err := json.Marshal(httpResponse) - if err != nil { - log.Println("Error marshaling response", "error", err) - http.Error(w, errorResponse("Internal Server Error"), http.StatusInternalServerError) - return - } - - _, err = w.Write(responseJSON) - if err != nil { - http.Error(w, errorResponse("Internal Server Error"), http.StatusInternalServerError) - return - } -} - -func (s *HTTPServer) SearchHandler(w http.ResponseWriter, request *http.Request) { - util.JSONResponse(w, http.StatusOK, map[string]string{"message": "Search results"}) -} diff --git a/internal/service/cli.go b/internal/service/cli.go new file mode 100644 index 0000000..66bae7d --- /dev/null +++ b/internal/service/cli.go @@ -0,0 +1,53 @@ +package service + +import ( + "context" + "errors" + "server/internal/repository" +) + +func (s *service) Get(ctx context.Context, req *GetRequest) (*GetResponse, error) { + if req.Key == "" { + return &GetResponse{}, errors.New("key is required") + } + + value, err := s.repo.Get(ctx, &repository.GetRequest{ + Key: req.Key, + }) + if err != nil { + return &GetResponse{}, err + } + + return &GetResponse{Value: value}, nil +} + +func (s *service) Set(ctx context.Context, req *SetRequest) (*SetResponse, error) { + if req.Key == "" || req.Value == "" { + return nil, errors.New("key and value are required") + } + + err := s.repo.Set(ctx, &repository.SetRequest{ + Key: req.Key, + Value: req.Value, + }) + if err != nil { + return nil, err + } + + return &SetResponse{Success: "OK"}, nil +} + +func (s *service) Delete(ctx context.Context, req *DeleteRequest) (*DeleteResponse, error) { + if len(req.Keys) == 0 { + return nil, errors.New("at least one key is required") + } + + err := s.repo.Delete(ctx, &repository.DeleteRequest{ + Keys: req.Keys, + }) + if err != nil { + return nil, err + } + + return &DeleteResponse{Success: "OK"}, nil +} diff --git a/internal/service/health.go b/internal/service/health.go new file mode 100644 index 0000000..fe1e29a --- /dev/null +++ b/internal/service/health.go @@ -0,0 +1,3 @@ +package service + +// TODO: Implement me diff --git a/internal/service/search.go b/internal/service/search.go new file mode 100644 index 0000000..fe1e29a --- /dev/null +++ b/internal/service/search.go @@ -0,0 +1,3 @@ +package service + +// TODO: Implement me diff --git a/internal/service/types.go b/internal/service/types.go new file mode 100644 index 0000000..e6b28e6 --- /dev/null +++ b/internal/service/types.go @@ -0,0 +1,57 @@ +package service + +import ( + "context" + "server/internal/repository" +) + +// Service is service layer client interface that should have all the methods +// exposed to handlers +type Service interface { + Get(ctx context.Context, req *GetRequest) (*GetResponse, error) + Set(ctx context.Context, req *SetRequest) (*SetResponse, error) + Delete(ctx context.Context, req *DeleteRequest) (*DeleteResponse, error) +} + +// service is private struct and concrete implementation that implements the +// Service client level interface +type service struct { + repo repository.Repository +} + +// NewService instantiates the service layer it takes Repository layer's client +// interface as dependency +func NewService(repo repository.Repository) Service { + return &service{repo: repo} +} + +// Service layer response and request models for GET + +type GetRequest struct { + Key string +} + +type GetResponse struct { + Value string +} + +// Service layer response and request models for SET + +type SetRequest struct { + Key string + Value string +} + +type SetResponse struct { + Success string +} + +// Service layer response and request models for DELETE + +type DeleteRequest struct { + Keys []string +} + +type DeleteResponse struct { + Success string +} diff --git a/main.go b/main.go index 09a082f..bf7c752 100644 --- a/main.go +++ b/main.go @@ -1,34 +1,17 @@ package main import ( - "context" "log" - "net/http" "server/config" - "server/internal/db" - "server/internal/server" // Import the new package for HTTPServer + "server/internal/server" ) func main() { - configValue := config.LoadConfig() - diceClient, err := db.InitDiceClient(configValue) - if err != nil { - log.Fatalf("Failed to initialize dice client: %v", err) - } - - // Create mux and register routes - mux := http.NewServeMux() - httpServer := server.NewHTTPServer(":8080", mux, diceClient, configValue.RequestLimit, configValue.RequestWindow) - mux.HandleFunc("/health", httpServer.HealthCheck) - mux.HandleFunc("/cli/{cmd}", httpServer.CliHandler) - mux.HandleFunc("/search", httpServer.SearchHandler) - - // Graceful shutdown context - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + configValue := config.Load() + srv := server.NewServer(configValue) - // Run the HTTP Server - if err := httpServer.Run(ctx); err != nil { - log.Printf("Server failed: %v\n", err) + if err := srv.Run(); err != nil { + // Crash now + log.Fatalf("Server error: %v", err) } } diff --git a/pkg/util/helpers.go b/pkg/common/common.go similarity index 100% rename from pkg/util/helpers.go rename to pkg/common/common.go From 96cee45c762909b29e46c5ce5eea44acf31557b1 Mon Sep 17 00:00:00 2001 From: Swarit Pandey Date: Tue, 1 Oct 2024 17:30:13 +0000 Subject: [PATCH 2/3] chore: rebase, fix mod file Signed-off-by: Swarit Pandey --- go.mod | 7 ++++--- go.sum | 10 ++++++---- internal/cmds/cmds.go | 6 ++++++ internal/handlers/handlers.go | 34 +++++++++++++++++++++------------- internal/server/http.go | 11 +++++------ internal/service/cli.go | 3 ++- internal/service/types.go | 3 ++- main.go | 5 +++-- pkg/common/common.go | 5 +++-- 9 files changed, 52 insertions(+), 32 deletions(-) diff --git a/go.mod b/go.mod index 26906be..c963f7f 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,10 @@ -module server +module github.com/DiceDB/playground-mono -go 1.22.5 +go 1.23.0 + +require github.com/dicedb/go-dice v0.0.0-20240929141018-7ef25848d647 require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/dicedb/go-dice v0.0.0-20240820180649-d97f15fca831 // indirect ) diff --git a/go.sum b/go.sum index af5b6c5..0b6badb 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,10 @@ +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/dicedb/go-dice v0.0.0-20240820180649-d97f15fca831 h1:Cqyj9WCtoobN6++bFbDSe27q94SPwJD9Z0wmu+SDRuk= -github.com/dicedb/go-dice v0.0.0-20240820180649-d97f15fca831/go.mod h1:8+VZrr14c2LW8fW4tWZ8Bv3P2lfvlg+PpsSn5cWWuiQ= +github.com/dicedb/go-dice v0.0.0-20240929141018-7ef25848d647 h1:tJZD5LdR6GoBZZtBn0bD54xChnnXbedXGjOlCthTw0o= +github.com/dicedb/go-dice v0.0.0-20240929141018-7ef25848d647/go.mod h1:8+VZrr14c2LW8fW4tWZ8Bv3P2lfvlg+PpsSn5cWWuiQ= diff --git a/internal/cmds/cmds.go b/internal/cmds/cmds.go index bb7a275..e139777 100644 --- a/internal/cmds/cmds.go +++ b/internal/cmds/cmds.go @@ -4,3 +4,9 @@ type CommandRequest struct { Cmd string `json:"cmd"` Args []string } + +type CommandRequestArgs struct { + Key string `json:"key"` + Value string `json:"value,omitempty"` + Keys []string `json:"keys,omitempty"` +} diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index e9746f0..a6d7d84 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -2,8 +2,9 @@ package handlers import ( "net/http" - "server/internal/service" - "server/pkg/common" + + "github.com/DiceDB/playground-mono/internal/service" + "github.com/DiceDB/playground-mono/pkg/common" ) const ( @@ -30,28 +31,35 @@ func (h *handler) CLIHandler(w http.ResponseWriter, r *http.Request) { command, err := common.ParseHTTPRequest(r) if err != nil { common.JSONResponse(w, http.StatusBadRequest, map[string]string{"error": err.Error()}) + return } var result any switch command.Cmd { - case OpGET: - result, err = h.service.Get(r.Context(), &service.GetRequest{Key: command.Args.Key}) + if len(command.Args) < 1 { + common.JSONResponse(w, http.StatusBadRequest, map[string]string{"error": "key is required for GET operation"}) + return + } + result, err = h.service.Get(r.Context(), &service.GetRequest{Key: command.Args[0]}) case OpSET: - result, err = h.service.Set(r.Context(), &service.SetRequest{Key: command.Args.Key, Value: command.Args.Value}) + if len(command.Args) < 2 { + common.JSONResponse(w, http.StatusBadRequest, map[string]string{"error": "key and value are required for SET operation"}) + return + } + result, err = h.service.Set(r.Context(), &service.SetRequest{Key: command.Args[0], Value: command.Args[1]}) case OpDEL: - var keys []string - if command.Args.Key != "" { - keys = []string{command.Args.Key} - } else if len(command.Args.Keys) > 0 { - keys = command.Args.Keys - } else { - common.JSONResponse(w, http.StatusBadRequest, map[string]string{"error": "at least one key is required"}) + if len(command.Args) < 1 { + common.JSONResponse(w, http.StatusBadRequest, map[string]string{"error": "at least one key is required for DELETE operation"}) return } - result, err = h.service.Delete(r.Context(), &service.DeleteRequest{Keys: keys}) + result, err = h.service.Delete(r.Context(), &service.DeleteRequest{Keys: command.Args}) + + default: + common.JSONResponse(w, http.StatusBadRequest, map[string]string{"error": "unknown command"}) + return } if err != nil { diff --git a/internal/server/http.go b/internal/server/http.go index b1d04d3..d06cdff 100644 --- a/internal/server/http.go +++ b/internal/server/http.go @@ -11,12 +11,11 @@ import ( "syscall" "time" - "server/config" - "server/internal/handlers" - "server/internal/middleware" - "server/internal/repository" - "server/internal/service" - + "github.com/DiceDB/playground-mono/config" + "github.com/DiceDB/playground-mono/internal/handlers" + "github.com/DiceDB/playground-mono/internal/middleware" + "github.com/DiceDB/playground-mono/internal/repository" + "github.com/DiceDB/playground-mono/internal/service" dice "github.com/dicedb/go-dice" ) diff --git a/internal/service/cli.go b/internal/service/cli.go index 66bae7d..a9fa06b 100644 --- a/internal/service/cli.go +++ b/internal/service/cli.go @@ -3,7 +3,8 @@ package service import ( "context" "errors" - "server/internal/repository" + + "github.com/DiceDB/playground-mono/internal/repository" ) func (s *service) Get(ctx context.Context, req *GetRequest) (*GetResponse, error) { diff --git a/internal/service/types.go b/internal/service/types.go index e6b28e6..f23626c 100644 --- a/internal/service/types.go +++ b/internal/service/types.go @@ -2,7 +2,8 @@ package service import ( "context" - "server/internal/repository" + + "github.com/DiceDB/playground-mono/internal/repository" ) // Service is service layer client interface that should have all the methods diff --git a/main.go b/main.go index bf7c752..ce4bb41 100644 --- a/main.go +++ b/main.go @@ -2,8 +2,9 @@ package main import ( "log" - "server/config" - "server/internal/server" + + "github.com/DiceDB/playground-mono/config" + "github.com/DiceDB/playground-mono/internal/server" ) func main() { diff --git a/pkg/common/common.go b/pkg/common/common.go index ce826f8..d802210 100644 --- a/pkg/common/common.go +++ b/pkg/common/common.go @@ -1,4 +1,4 @@ -package helpers +package common import ( "encoding/json" @@ -6,8 +6,9 @@ import ( "fmt" "io" "net/http" - "server/internal/cmds" "strings" + + "github.com/DiceDB/playground-mono/internal/cmds" ) const ( From 6cc7c1b8b28768a8fbc94d729a87c3de01362551 Mon Sep 17 00:00:00 2001 From: Swarit Pandey Date: Tue, 1 Oct 2024 17:31:32 +0000 Subject: [PATCH 3/3] chore: comment Signed-off-by: Swarit Pandey --- config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.go b/config/config.go index 0bc5630..2d0e53d 100644 --- a/config/config.go +++ b/config/config.go @@ -13,7 +13,7 @@ type Config struct { RequestWindow int // Field for the time window in seconds } -// LoadConfig loads the application configuration from environment variables or defaults +// Load loads the application configuration from environment variables or defaults func Load() *Config { return &Config{ DiceAddr: getEnv("DICE_ADDR", "localhost:7379"), // Default Dice address