diff --git a/cmd/shoppingbasketapp/command/root.go b/cmd/shoppingbasketapp/command/root.go index d47dcf0d..e2634743 100644 --- a/cmd/shoppingbasketapp/command/root.go +++ b/cmd/shoppingbasketapp/command/root.go @@ -1 +1,52 @@ package command + +import ( + cfgloader "git.gocasts.ir/ebhomengo/niki/pkg/cfg_loader" + "git.gocasts.ir/ebhomengo/niki/pkg/path" + "git.gocasts.ir/ebhomengo/niki/shoppingbasketapp" + "github.com/spf13/cobra" + "log" + "os" + "path/filepath" +) + +var RootCmd = &cobra.Command{ + Use: "shoppingbasket_service", + Short: "A CLI for shoppingbasket service", + Long: `shoppingbasket Service CLI is a tool to manage and run +the shoppingbasket service, including migrations and server startup.`, +} + +func loadAppConfig() shoppingbasketapp.Config { + var cfg shoppingbasketapp.Config + + projectRoot, err := path.PathProjectRoot() + if err != nil { + log.Fatalf("error finding project root: %v", err) + } + + yamlPath := os.Getenv("CONFIG_PATH") + + if yamlPath == "" { + defaultConfig := filepath.Join(projectRoot, "deploy", "shoppingbasket", "development", "config.yml") + if _, err := os.Stat(defaultConfig); err == nil { + yamlPath = defaultConfig + } else { + yamlPath = filepath.Join(projectRoot, "deploy", "shoppingbasket", "development", "config.local.yml") + } + } + + options := cfgloader.Option{ + Prefix: "SHOPPINGBASKET_", + Delimiter: ".", + Separator: "__", + YamlFilePath: yamlPath, + CallbackEnv: nil, + } + + if err := cfgloader.Load(options, &cfg); err != nil { + log.Fatalf("Failed to load benefactor config: %v", err) + } + + return cfg +} diff --git a/cmd/shoppingbasketapp/command/serve.go b/cmd/shoppingbasketapp/command/serve.go index d47dcf0d..8196c023 100644 --- a/cmd/shoppingbasketapp/command/serve.go +++ b/cmd/shoppingbasketapp/command/serve.go @@ -1 +1,43 @@ package command + +import ( + "context" + "fmt" + "git.gocasts.ir/ebhomengo/niki/pkg/logger" + "git.gocasts.ir/ebhomengo/niki/shoppingbasketapp" + "github.com/labstack/gommon/log" + "github.com/spf13/cobra" +) + +var ServeCmd = &cobra.Command{ + Use: "serve", + Short: "Start shoppingbasket service", + Long: `This command starts the main shoppingbasket service.`, + Run: func(cmd *cobra.Command, args []string) { + + }, +} + +func serve() { + var cfg = loadAppConfig() + + logger.Init(cfg.Logger) + l := logger.L() + + l.Info("Starting shoppingbasket service...") + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + app, err := shoppingbasketapp.Setup(ctx, cfg) + if err != nil { + l.Error("failed initialize shopping basket app", "error", err) + log.Fatalf(fmt.Sprintf("error starting shopping basket app: %v", err)) + } + + app.Start() +} + +func init() { + RootCmd.AddCommand(ServeCmd) +} diff --git a/cmd/shoppingbasketapp/main.go b/cmd/shoppingbasketapp/main.go index 98de2812..d204ab4a 100644 --- a/cmd/shoppingbasketapp/main.go +++ b/cmd/shoppingbasketapp/main.go @@ -1,44 +1,12 @@ package main import ( - "fmt" - "git.gocasts.ir/ebhomengo/niki/adapter/redis" - "git.gocasts.ir/ebhomengo/niki/logger" - "git.gocasts.ir/ebhomengo/niki/pkg/httpserver" - "git.gocasts.ir/ebhomengo/niki/shoppingbasketapp" - "git.gocasts.ir/ebhomengo/niki/shoppingbasketapp/repository" - "time" + "git.gocasts.ir/ebhomengo/niki/cmd/shoppingbasketapp/command" + "os" ) func main() { - cfg := shoppingbasketapp.Config{ - Redis: redis.Config{ - Host: "localhost", - Port: 6379, - Password: "", - DB: 0, - }, - Repo: repository.Config{ - KartKeyPrefix: "shopping-basket-cart:", - TTL: 3600 * time.Second, - }, - HTTPServer: httpserver.Config{ - Host: "localhost", - Port: 8080, - ShutdownTimeout: 10 * time.Second, - }, - Logger: logger.Config{ - FilePath: "cmd/shoppingbasketapp/logs/service.log", - UseLocalTime: true, - FileMaxSizeInMB: 10, - FileMaxAgeInDays: 30, - }, + if err := command.RootCmd.Execute(); err != nil { + os.Exit(1) } - - app, err := shoppingbasketapp.Setup(cfg) - if err != nil { - panic(fmt.Sprintf("error initialize to setup app: %s", err.Error())) - } - - app.Start() } diff --git a/deploy/shoppingbasket/development/config.yml b/deploy/shoppingbasket/development/config.local.yml similarity index 60% rename from deploy/shoppingbasket/development/config.yml rename to deploy/shoppingbasket/development/config.local.yml index 7a94a5fc..eecfaa99 100644 --- a/deploy/shoppingbasket/development/config.yml +++ b/deploy/shoppingbasket/development/config.local.yml @@ -12,9 +12,14 @@ http_server: host: "localhost" port: 8080 shutdown_context_timeout: 10s + cors: + allow_origins: + - "*" + logger: - file_path: "cmd/shoppingbasketapp/logs/service.log" + level: "debug" # Can be `debug`, `info`, `warn`, `error` + file_path: "logs/shoppingbasketapp/service.log" use_local_time: true file_max_size_in_mb: 10 - file_max_age_in_days: 30 + file_max_age_in_days: 7 diff --git a/shoppingbasketapp/app.go b/shoppingbasketapp/app.go index 734b127c..2ce4df1e 100644 --- a/shoppingbasketapp/app.go +++ b/shoppingbasketapp/app.go @@ -4,12 +4,11 @@ import ( "context" "fmt" "git.gocasts.ir/ebhomengo/niki/adapter/redis" - logger "git.gocasts.ir/ebhomengo/niki/logger" "git.gocasts.ir/ebhomengo/niki/pkg/httpserver" + "git.gocasts.ir/ebhomengo/niki/pkg/logger" "git.gocasts.ir/ebhomengo/niki/shoppingbasketapp/delivery/http" "git.gocasts.ir/ebhomengo/niki/shoppingbasketapp/repository" "git.gocasts.ir/ebhomengo/niki/shoppingbasketapp/service/cart" - "log/slog" "os" "os/signal" "sync" @@ -22,25 +21,21 @@ type Application struct { Handler http.Handler Server http.Server Config Config - Logger *slog.Logger } -func Setup(cfg Config) (Application, error) { - l := logger.New(cfg.Logger, &slog.HandlerOptions{ - Level: slog.LevelDebug, - }) +func Setup(ctx context.Context, cfg Config) (Application, error) { adapter := redis.New(cfg.Redis) repo := repository.New(adapter.Client(), cfg.Repo) validator := cart.NewValidate() - svc := cart.New(validator, l, repo) + svc := cart.New(validator, repo) handler := http.NewHandler(svc) httpServer, err := httpserver.New(cfg.HTTPServer) if err != nil { - l.Error("failed to initialize http server", "error", err) + logger.L().Error("failed to initialize http server", "error", err) return Application{}, err } server := http.NewServer(handler, httpServer) @@ -51,7 +46,6 @@ func Setup(cfg Config) (Application, error) { Handler: handler, Server: server, Config: cfg, - Logger: l, }, nil } @@ -64,37 +58,37 @@ func (app Application) Start() { startServers(app, &wg) <-ctx.Done() - app.Logger.Info("Shutdown signal received...") + logger.L().Info("Shutdown signal received...") shutdownTimeoutCtx, cancel := context.WithTimeout(context.Background(), app.Config.HTTPServer.ShutdownTimeout) defer cancel() if app.shutdownServers(shutdownTimeoutCtx) { - app.Logger.Info("Servers shutdown gracefully") + logger.L().Info("Servers shutdown gracefully") } else { - app.Logger.Warn("Shutdown timed out, exiting application") + logger.L().Warn("Shutdown timed out, exiting application") return } wg.Wait() - app.Logger.Info("shopping-basket-app stopped") + logger.L().Info("shopping-basket-app stopped") } func startServers(app Application, wg *sync.WaitGroup) { wg.Add(1) go func() { defer wg.Wait() - app.Logger.Info(fmt.Sprintf("HTTP server starting on port: %d", app.Config.HTTPServer.Port)) + logger.L().Info(fmt.Sprintf("HTTP server starting on port: %d", app.Config.HTTPServer.Port)) if err := app.Server.Serve(); err != nil { - app.Logger.Error(fmt.Sprintf("error listen and serve HTTP server on port %d", app.Config.HTTPServer.Port)) + logger.L().Error(fmt.Sprintf("error listen and serve HTTP server on port %d", app.Config.HTTPServer.Port)) } - app.Logger.Info(fmt.Sprintf("HTTP server stopped on port %d", app.Config.HTTPServer.Port)) + logger.L().Info(fmt.Sprintf("HTTP server stopped on port %d", app.Config.HTTPServer.Port)) }() } func (app Application) shutdownServers(ctx context.Context) bool { - app.Logger.Info("Starting server shutdown process...") + logger.L().Info("Starting server shutdown process...") shutdownDone := make(chan struct{}) @@ -105,7 +99,7 @@ func (app Application) shutdownServers(ctx context.Context) bool { shutdownWg.Wait() close(shutdownDone) - app.Logger.Info("All servers have been shut down successfully.") + logger.L().Info("All servers have been shut down successfully.") }() @@ -118,15 +112,15 @@ func (app Application) shutdownServers(ctx context.Context) bool { } func (app Application) shutdownHTTPServe(parentCtx context.Context, wg *sync.WaitGroup) { - app.Logger.Info(fmt.Sprintf("Starting gracefully shutdown for http server on port %d", app.Config.HTTPServer.Port)) + logger.L().Info(fmt.Sprintf("Starting gracefully shutdown for http server on port %d", app.Config.HTTPServer.Port)) defer wg.Done() httpShutdownCtx, httpCancel := context.WithTimeout(parentCtx, app.Config.HTTPServer.ShutdownTimeout) defer httpCancel() if err := app.Server.Stop(httpShutdownCtx); err != nil { - app.Logger.Error(fmt.Sprintf("failed http server gracefully shutdown: %v", err)) + logger.L().Error(fmt.Sprintf("failed http server gracefully shutdown: %v", err)) } - app.Logger.Info("Successfully http server gracefully shutdown") + logger.L().Info("Successfully http server gracefully shutdown") } diff --git a/shoppingbasketapp/config.go b/shoppingbasketapp/config.go index 39a0e4af..291c301f 100644 --- a/shoppingbasketapp/config.go +++ b/shoppingbasketapp/config.go @@ -2,8 +2,8 @@ package shoppingbasketapp import ( "git.gocasts.ir/ebhomengo/niki/adapter/redis" - "git.gocasts.ir/ebhomengo/niki/logger" "git.gocasts.ir/ebhomengo/niki/pkg/httpserver" + logger "git.gocasts.ir/ebhomengo/niki/pkg/logger" "git.gocasts.ir/ebhomengo/niki/shoppingbasketapp/repository" ) diff --git a/shoppingbasketapp/service/cart/service.go b/shoppingbasketapp/service/cart/service.go index d84a3f73..1e2da37e 100644 --- a/shoppingbasketapp/service/cart/service.go +++ b/shoppingbasketapp/service/cart/service.go @@ -2,9 +2,9 @@ package cart import ( "context" + "git.gocasts.ir/ebhomengo/niki/pkg/logger" richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error" "git.gocasts.ir/ebhomengo/niki/types" - "log/slog" "time" ) @@ -18,19 +18,18 @@ type Repository interface { type Service struct { validate Validate - logger *slog.Logger repo Repository } -func New(val Validate, logger *slog.Logger, repo Repository) Service { - return Service{validate: val, logger: logger, repo: repo} +func New(val Validate, repo Repository) Service { + return Service{validate: val, repo: repo} } func (s Service) AddToBasket(ctx context.Context, req AddToCartRequest) error { const op = "shoppingbasketapp.service.AddToBasket" if err := s.validate.ValidateAddToCart(req); err != nil { - s.logger.Error("shoppingbasket-service-AddToBasket", "error", err) + logger.L().Error("shoppingbasket-service-AddToBasket", "error", err) return err } @@ -46,13 +45,13 @@ func (s Service) AddToBasket(ctx context.Context, req AddToCartRequest) error { func (s Service) GetCart(ctx context.Context, userID types.ID) (GetCartResponse, error) { const op = "shoppingbasketapp.service.GetCart" if userID < 1 { - s.logger.Error("shoppingbasket-service-GetCart", "error", "user id must be greater than 1") + logger.L().Error("shoppingbasket-service-GetCart", "error", "user id must be greater than 1") return GetCartResponse{}, richerror.New(op).WithKind(richerror.KindInvalid).WithMessage("invalid user id") } res, err := s.repo.GetCart(ctx, userID) if err != nil { - s.logger.Error("shoppingbasket-service-GetCart", "error", err) + logger.L().Error("shoppingbasket-service-GetCart", "error", err) return GetCartResponse{}, richerror.New(op).WithErr(err) } @@ -69,7 +68,7 @@ func (s Service) RemoveFromCart(ctx context.Context, req RemoveFromCartRequest) const op = "shoppingbaskerapp.service.RemoveFromCart" if err := s.validate.ValidateRemoveFromCart(req); err != nil { - s.logger.Error("shoppingbasket-service-RemoveFromCart", "error", err) + logger.L().Error("shoppingbasket-service-RemoveFromCart", "error", err) return err } @@ -80,7 +79,7 @@ func (s Service) UpdateQuantity(ctx context.Context, req UpdateQuantityRequest) const op = "shoppingbaskerapp.service.UpdateQuantity" if err := s.validate.ValidateUpdateQuantity(req); err != nil { - s.logger.Error("shoppingbasket-service-UpdateQuantity", "error", err) + logger.L().Error("shoppingbasket-service-UpdateQuantity", "error", err) return err } @@ -95,7 +94,7 @@ func (s Service) ClearCart(ctx context.Context, userID types.ID) error { const op = "shoppingbaskerapp.service.ClearCart" if userID < 1 { - s.logger.Error("shoppingbasket-service-ClearCart", "error", "user id must be greater than 1") + logger.L().Error("shoppingbasket-service-ClearCart", "error", "user id must be greater than 1") return richerror.New(op).WithKind(richerror.KindInvalid). WithMessage("invalid user id") }