diff --git a/benefactorapp/app.go b/benefactorapp/app.go
index 3a01a26f..6c77ca9d 100644
--- a/benefactorapp/app.go
+++ b/benefactorapp/app.go
@@ -1,8 +1,62 @@
package benefactorapp
-import "net/http"
+import (
+ "context"
+ benefactorHTTP "git.gocasts.ir/ebhomengo/niki/benefactorapp/delivery/http"
+ repo "git.gocasts.ir/ebhomengo/niki/benefactorapp/repository/database"
+ benefactor "git.gocasts.ir/ebhomengo/niki/benefactorapp/service"
+ mySql "git.gocasts.ir/ebhomengo/niki/pkg/database/mysql"
+ httpserver "git.gocasts.ir/ebhomengo/niki/pkg/httpserver"
+ logger "git.gocasts.ir/ebhomengo/niki/pkg/logger"
+)
type Application struct {
- Config Config
- HTTPServer *http.Server
+ Config Config
+ HTTPServer benefactorHTTP.Server
+ BenefactorService benefactor.Service
+ BenefactorHandler benefactorHTTP.Handler
+ BenefactorRepo benefactor.Repository
+ DBConn mySql.DB
+}
+
+func Setup(ctx context.Context, config Config, DB mySql.DB) *Application {
+ log := logger.L()
+ log.Info("logger starting ...")
+
+ db := mySql.New(config.MySQLDB)
+ defer func() {
+ if err := db.CloseStatements(); err != nil {
+ log.Info("Error closing statements: %v\n", err)
+ }
+ }()
+ log.Info("mysql connection starting ...")
+
+ // Initialize repositories
+ benefactorRepo := repo.New(db)
+ benefactorValidator := benefactor.NewValidator(benefactorRepo)
+
+ benefactorSvc := benefactor.NewService(benefactorRepo, benefactorValidator)
+ benefactorHandler := benefactorHTTP.NewHandler(benefactorSvc)
+
+ hServer, hErr := httpserver.New(config.HTTPServer)
+ if hErr != nil {
+ log.Error("Http Server error: %v,\n", hErr)
+ }
+ httpServer := benefactorHTTP.NewServer(*hServer, *benefactorHandler)
+
+ return &Application{
+ Config: config,
+ HTTPServer: httpServer,
+ BenefactorService: benefactorSvc,
+ BenefactorHandler: *benefactorHandler,
+ BenefactorRepo: benefactorRepo,
+ DBConn: DB,
+ }
+
+}
+
+func (app *Application) Start() {
+ log := logger.L()
+ log.Info("app starting ...")
+ // TODO implementaion
}
diff --git a/benefactorapp/config.go b/benefactorapp/config.go
index de3d3f88..fb8f9376 100644
--- a/benefactorapp/config.go
+++ b/benefactorapp/config.go
@@ -1,11 +1,23 @@
package benefactorapp
+import (
+ database "git.gocasts.ir/ebhomengo/niki/pkg/database/mysql"
+ httpserver "git.gocasts.ir/ebhomengo/niki/pkg/httpserver"
+ "git.gocasts.ir/ebhomengo/niki/pkg/logger"
+)
+
type Config struct {
// HTTP server config
+ HTTPServer httpserver.Config `koanf:"http_server"`
// Database config
+ MySQLDB database.Config `koanf:"mysql_db"`
// Logger config
+ Logger logger.Config `koanf:"logger"`
// Service config
+
+ // Database migration
+ PathOfMigration string `koanf:"path_of_migration"`
}
diff --git a/benefactorapp/delivery/http/handler.go b/benefactorapp/delivery/http/handler.go
index e9549c3c..c3966ddb 100644
--- a/benefactorapp/delivery/http/handler.go
+++ b/benefactorapp/delivery/http/handler.go
@@ -1,15 +1,20 @@
package http
import (
+ benefactor "git.gocasts.ir/ebhomengo/niki/benefactorapp/service"
"net/http"
"github.com/labstack/echo/v4"
)
-type Handler struct{}
+type Handler struct {
+ BebefactorService benefactor.Service
+}
-func NewHandler() *Handler {
- return &Handler{}
+func NewHandler(bService benefactor.Service) *Handler {
+ return &Handler{
+ BebefactorService: bService,
+ }
}
func (h Handler) HealthCheck(c echo.Context) error {
diff --git a/benefactorapp/delivery/http/server.go b/benefactorapp/delivery/http/server.go
index c5adfb44..4bdf3e65 100644
--- a/benefactorapp/delivery/http/server.go
+++ b/benefactorapp/delivery/http/server.go
@@ -1,16 +1,16 @@
package http
-import httpserver "git.gocasts.ir/ebhomengo/niki/delivery/http_server"
+import httpserver "git.gocasts.ir/ebhomengo/niki/pkg/httpserver"
type Server struct {
- HTTPServer *httpserver.Server
- Handler *Handler
+ HTTPServer httpserver.Server
+ Handler Handler
}
-func NewServer(httpserver *httpserver.Server) *Server {
- return &Server{
+func NewServer(httpserver httpserver.Server, handler Handler) Server {
+ return Server{
HTTPServer: httpserver,
- Handler: NewHandler(),
+ Handler: handler,
}
}
diff --git a/benefactorapp/repository/database/db.go b/benefactorapp/repository/database/db.go
index 5d07c611..e3fef1fb 100644
--- a/benefactorapp/repository/database/db.go
+++ b/benefactorapp/repository/database/db.go
@@ -1,6 +1,6 @@
package database
-import "git.gocasts.ir/ebhomengo/niki/repository/mysql"
+import "git.gocasts.ir/ebhomengo/niki/pkg/database/mysql"
type DB struct {
conn *mysql.DB
diff --git a/benefactorapp/service/service.go b/benefactorapp/service/service.go
index 6d43c336..5cd2167b 100644
--- a/benefactorapp/service/service.go
+++ b/benefactorapp/service/service.go
@@ -1 +1,17 @@
package service
+
+type Service struct {
+ repository Repository
+ validator Validator
+}
+
+type Repository interface {
+ //GetList(ctx context.Context, ID types.ID) ([]entity.Benefactor, error)
+}
+
+func NewService(repo Repository, validator Validator) Service {
+ return Service{
+ repository: repo,
+ validator: validator,
+ }
+}
diff --git a/benefactorapp/service/validator.go b/benefactorapp/service/validator.go
index 6d43c336..21bc13dd 100644
--- a/benefactorapp/service/validator.go
+++ b/benefactorapp/service/validator.go
@@ -1 +1,11 @@
package service
+
+type ValidatorBenefactorRepository interface {
+}
+type Validator struct {
+ repo ValidatorBenefactorRepository
+}
+
+func NewValidator(repo ValidatorBenefactorRepository) Validator {
+ return Validator{repo: repo}
+}
diff --git a/cmd/benefactor/command/migrate.go b/cmd/benefactor/command/migrate.go
new file mode 100644
index 00000000..46257b78
--- /dev/null
+++ b/cmd/benefactor/command/migrate.go
@@ -0,0 +1,53 @@
+package command
+
+import (
+ migrator "git.gocasts.ir/ebhomengo/niki/pkg/database/migrator"
+ "git.gocasts.ir/ebhomengo/niki/pkg/logger"
+ "github.com/spf13/cobra"
+)
+
+var up bool
+var down bool
+
+var migrateCmd = &cobra.Command{
+ Use: "migrate",
+ Short: "Run database migrations",
+ Long: `This command runs the database migrations for the benefactor service.`,
+ Run: func(cmd *cobra.Command, args []string) {
+ migrate()
+ },
+}
+
+func migrate() {
+ var cfg = loadAppConfig()
+ mysqlConfig := getMysqlConfig()
+
+ logger.Init(cfg.Logger)
+ log := logger.L()
+
+ migratorCfg := migrator.Config{
+ MysqlConfig: mysqlConfig,
+ MigrationPath: cfg.PathOfMigration,
+ MigrationDBName: "gorp_migrations",
+ }
+ // Run migrations if flags are set
+ if migrateUp || migrateDown {
+ mgr := migrator.New(migratorCfg)
+ if migrateUp {
+ log.Info("Running migrations up...")
+ mgr.Up()
+ log.Info("Migrations up completed.")
+ }
+ if migrateDown {
+ log.Info("Running migrations down...")
+ mgr.Down()
+ log.Info("Migrations down completed.")
+ }
+ }
+}
+
+func init() {
+ migrateCmd.Flags().BoolVar(&up, "up", false, "Run migrations up")
+ migrateCmd.Flags().BoolVar(&down, "down", false, "Run migrations down")
+ RootCmd.AddCommand(migrateCmd)
+}
diff --git a/cmd/benefactor/command/root.go b/cmd/benefactor/command/root.go
new file mode 100644
index 00000000..9d2a4ec0
--- /dev/null
+++ b/cmd/benefactor/command/root.go
@@ -0,0 +1,77 @@
+package command
+
+import (
+ "fmt"
+ benefactorapp "git.gocasts.ir/ebhomengo/niki/benefactorapp"
+ cfgloader "git.gocasts.ir/ebhomengo/niki/pkg/cfg_loader"
+ "git.gocasts.ir/ebhomengo/niki/pkg/database/mysql"
+ serviceConfigMysql "git.gocasts.ir/ebhomengo/niki/pkg/database/mysql"
+ "git.gocasts.ir/ebhomengo/niki/pkg/path"
+ "github.com/spf13/cobra"
+ "log"
+ "os"
+ "path/filepath"
+)
+
+var RootCmd = &cobra.Command{
+ Use: "benefactor_service",
+ Short: "A CLI for benefactor Service",
+ Long: `benefactor Service CLI is a tool to manage and run
+the benefactor service, including migrations and server startup.`,
+}
+
+func getMysqlConfig() mysql.Config {
+ var cfg = loadAppConfig()
+
+ mysqlConfig := serviceConfigMysql.Config{
+ Username: cfg.MySQLDB.Username,
+ Password: cfg.MySQLDB.Password,
+ Port: cfg.MySQLDB.Port,
+ Host: cfg.MySQLDB.Host,
+ DBName: cfg.MySQLDB.DBName,
+ }
+ return mysqlConfig
+}
+
+func getDB() *mysql.DB {
+ db := serviceConfigMysql.New(getMysqlConfig())
+ defer func() {
+ if err := db.CloseStatements(); err != nil {
+ fmt.Printf("Error closing statements: %v\n", err)
+ }
+ }()
+ return db
+}
+func loadAppConfig() benefactorapp.Config {
+ var cfg benefactorapp.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", "benefactor", "development", "config.yml")
+ if _, err := os.Stat(defaultConfig); err == nil {
+ yamlPath = defaultConfig
+ } else {
+ yamlPath = filepath.Join(projectRoot, "deploy", "benefactor", "development", "config.local.yml")
+ }
+ }
+
+ options := cfgloader.Option{
+ Prefix: "BENEFACTOR_",
+ 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/benefactor/command/serve.go b/cmd/benefactor/command/serve.go
new file mode 100644
index 00000000..dd1c2f99
--- /dev/null
+++ b/cmd/benefactor/command/serve.go
@@ -0,0 +1,47 @@
+package command
+
+import (
+ "context"
+ "git.gocasts.ir/ebhomengo/niki/benefactorapp"
+ "git.gocasts.ir/ebhomengo/niki/pkg/logger"
+ "github.com/spf13/cobra"
+)
+
+var migrateUp bool
+var migrateDown bool
+
+var serveCmd = &cobra.Command{
+ Use: "serve",
+ Short: "Start the benefactor service",
+ Long: `This command starts the main benefactor service.`,
+ Run: func(cmd *cobra.Command, args []string) {
+ serve()
+ },
+}
+
+func serve() {
+ var cfg = loadAppConfig()
+
+ // Initialize logger
+ logger.Init(cfg.Logger)
+ log := logger.L()
+
+ db := getDB()
+ migrate()
+
+ // Start the server
+ log.Info("Starting benefactor Service...")
+
+ // Connect to the database
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+
+ app := benefactorapp.Setup(ctx, cfg, *db)
+ app.Start()
+}
+
+func init() {
+ serveCmd.Flags().BoolVar(&migrateUp, "migrate-up", false, "Run migrations up before starting the server")
+ serveCmd.Flags().BoolVar(&migrateDown, "migrate-down", false, "Run migrations down before starting the server")
+ RootCmd.AddCommand(serveCmd)
+}
diff --git a/cmd/benefactor/main.go b/cmd/benefactor/main.go
new file mode 100644
index 00000000..e74e381f
--- /dev/null
+++ b/cmd/benefactor/main.go
@@ -0,0 +1,12 @@
+package main
+
+import (
+ "git.gocasts.ir/ebhomengo/niki/cmd/benefactor/command"
+ "os"
+)
+
+func main() {
+ if err := command.RootCmd.Execute(); err != nil {
+ os.Exit(1)
+ }
+}
diff --git a/go.mod b/go.mod
index 42258236..7b4af63e 100644
--- a/go.mod
+++ b/go.mod
@@ -7,6 +7,7 @@ require (
github.com/go-ozzo/ozzo-validation v3.6.0+incompatible
github.com/go-ozzo/ozzo-validation/v4 v4.3.0
github.com/go-sql-driver/mysql v1.9.3
+ github.com/gocasters/rankr v0.0.0-20260222055437-aadc1fdc6a1d
github.com/golang-jwt/jwt/v4 v4.5.2
github.com/kavenegar/kavenegar-go v0.0.0-20240205151018-77039f51467d
github.com/knadh/koanf v1.5.0
@@ -15,6 +16,7 @@ require (
github.com/ory/dockertest/v3 v3.12.0
github.com/redis/go-redis/v9 v9.18.0
github.com/rubenv/sql-migrate v1.8.1
+ github.com/spf13/cobra v1.10.2
github.com/stretchr/testify v1.11.1
github.com/swaggo/echo-swagger v1.5.2
github.com/swaggo/swag v1.16.6
@@ -23,7 +25,7 @@ require (
)
require (
- dario.cat/mergo v1.0.0 // indirect
+ dario.cat/mergo v1.0.2 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
github.com/KyleBanks/depth v1.2.1 // indirect
@@ -36,20 +38,21 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/docker/cli v27.4.1+incompatible // indirect
- github.com/docker/docker v27.1.1+incompatible // indirect
- github.com/docker/go-connections v0.5.0 // indirect
+ github.com/docker/docker v28.3.3+incompatible // indirect
+ github.com/docker/go-connections v0.6.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/fatih/structs v1.1.0 // indirect
- github.com/fsnotify/fsnotify v1.4.9 // indirect
+ github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-gorp/gorp/v3 v3.1.0 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/spec v0.20.9 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
- github.com/go-viper/mapstructure/v2 v2.1.0 // indirect
+ github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
+ github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/labstack/gommon v0.4.2 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
@@ -59,14 +62,15 @@ require (
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
- github.com/moby/sys/user v0.3.0 // indirect
+ github.com/moby/sys/user v0.4.0 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
- github.com/opencontainers/image-spec v1.1.0 // indirect
+ github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/opencontainers/runc v1.2.3 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
+ github.com/spf13/pflag v1.0.10 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/sv-tools/openapi v0.2.1 // indirect
github.com/swaggo/files/v2 v2.0.0 // indirect
diff --git a/go.sum b/go.sum
index 940950dd..47efff98 100644
--- a/go.sum
+++ b/go.sum
@@ -1,7 +1,7 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
-dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
+dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
+dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
@@ -58,6 +58,7 @@ github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un
github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
+github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
@@ -68,10 +69,10 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/docker/cli v27.4.1+incompatible h1:VzPiUlRJ/xh+otB75gva3r05isHMo5wXDfPRi5/b4hI=
github.com/docker/cli v27.4.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
-github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY=
-github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
-github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
-github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
+github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI=
+github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
+github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
@@ -84,8 +85,9 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
-github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
+github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs=
github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw=
@@ -117,8 +119,10 @@ github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
-github.com/go-viper/mapstructure/v2 v2.1.0 h1:gHnMa2Y/pIxElCH2GlZZ1lZSsn6XMtufpGyP1XxdC/w=
-github.com/go-viper/mapstructure/v2 v2.1.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
+github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
+github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
+github.com/gocasters/rankr v0.0.0-20260222055437-aadc1fdc6a1d h1:uFg0KexbouyuGSzmcU9bp/Y7Ml4NXNYjqsRcF/1hnpk=
+github.com/gocasters/rankr v0.0.0-20260222055437-aadc1fdc6a1d/go.mod h1:nupI7yU3J4wEdsa/dUte/z9apDKkUWeRXlK4dGfntcY=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
@@ -198,6 +202,8 @@ github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKe
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/hjson/hjson-go/v4 v4.0.0 h1:wlm6IYYqHjOdXH1gHev4VoXCaW20HdQAGCxdOEEg2cs=
github.com/hjson/hjson-go/v4 v4.0.0/go.mod h1:KaYt3bTw3zhBjYqnXkYywcYctk0A2nxeEFTse3rH13E=
+github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
+github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
@@ -278,8 +284,8 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
-github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo=
-github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
+github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
+github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -293,8 +299,8 @@ github.com/npillmayer/nestext v0.1.3/go.mod h1:h2lrijH8jpicr25dFY+oAJLyzlya6jhnu
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
-github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
-github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
+github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
+github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/opencontainers/runc v1.2.3 h1:fxE7amCzfZflJO2lHXf4y/y8M1BoAqp+FVmG19oYB80=
github.com/opencontainers/runc v1.2.3/go.mod h1:nSxcWUydXrsBZVYNSkTjoQ/N6rcyTtn+1SD5D4+kRIM=
github.com/ory/dockertest/v3 v3.12.0 h1:3oV9d0sDzlSQfHtIaB5k6ghUCVMVLpAY8hwrqoCyRCw=
@@ -337,6 +343,7 @@ github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZV
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rubenv/sql-migrate v1.8.1 h1:EPNwCvjAowHI3TnZ+4fQu3a915OpnQoPAjTXCGOy2U0=
github.com/rubenv/sql-migrate v1.8.1/go.mod h1:BTIKBORjzyxZDS6dzoiw6eAFYJ1iNlGAtjn4LGeVjS8=
+github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
@@ -346,8 +353,12 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
-github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
+github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
+github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
+github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
@@ -399,6 +410,7 @@ go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
+go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
diff --git a/pkg/cfg_loader/cfg_loader.go b/pkg/cfg_loader/cfg_loader.go
new file mode 100644
index 00000000..600e6824
--- /dev/null
+++ b/pkg/cfg_loader/cfg_loader.go
@@ -0,0 +1,62 @@
+package cfgloader
+
+import (
+ "github.com/knadh/koanf"
+ "log"
+ "strings"
+
+ "github.com/knadh/koanf/parsers/yaml"
+ "github.com/knadh/koanf/providers/env"
+ "github.com/knadh/koanf/providers/file"
+ "github.com/knadh/koanf/v2"
+)
+
+type Option struct {
+ Prefix string
+ Delimiter string
+ Separator string
+ YamlFilePath string
+ CallbackEnv func(string) string
+}
+
+// defaultCallbackEnv processes environment variable keys based on provided prefix and separator
+func defaultCallbackEnv(source, prefix, separator string) string {
+ base := strings.ToLower(strings.TrimPrefix(source, prefix))
+ return strings.ReplaceAll(base, separator, ".")
+}
+
+// Load function loads configuration from YAML file and environment variables based on provided options
+func Load(options Option, config interface{}) error {
+ k := koanf.New(options.Delimiter)
+
+ // Load configuration from YAML file if provided
+ if options.YamlFilePath != "" {
+ if err := k.Load(file.Provider(options.YamlFilePath), yaml.Parser()); err != nil {
+ log.Fatalf("Error loading config file: %v", err)
+ return err
+ }
+ }
+
+ // Define callback function for environment variables
+ callback := options.CallbackEnv
+ if callback == nil {
+ // Set default callback using the prefix and separator from options
+ callback = func(source string) string {
+ return defaultCallbackEnv(source, options.Prefix, options.Separator)
+ }
+ }
+
+ // Load environment variables with the specified prefix and callback
+ if err := k.Load(env.Provider(options.Prefix, options.Delimiter, callback), nil); err != nil {
+ log.Fatalf("Error loading environment variables: %v", err)
+ return err
+ }
+
+ // Unmarshal into provided config structure (passing address)
+ if err := k.Unmarshal("", &config); err != nil {
+ log.Fatalf("Error unmarshalling config: %v", err)
+ return err
+ }
+
+ return nil
+}
diff --git a/pkg/database/migrator/migrator.go b/pkg/database/migrator/migrator.go
new file mode 100644
index 00000000..3a1dde0b
--- /dev/null
+++ b/pkg/database/migrator/migrator.go
@@ -0,0 +1,69 @@
+package migrator
+
+import (
+ "database/sql"
+ "fmt"
+
+ "git.gocasts.ir/ebhomengo/niki/pkg/database/mysql"
+ migrate "github.com/rubenv/sql-migrate"
+)
+
+type Config struct {
+ MysqlConfig mysql.Config
+ MigrationPath string
+ MigrationDBName string
+}
+
+type Migrator struct {
+ cfg Config
+ dialect string
+ migrations *migrate.FileMigrationSource
+}
+
+// TODO - set migration table name
+// TODO - add limit to Up and Down method
+
+func New(cfg Config) Migrator {
+ // OR: Read migrations from a folder:
+ migrations := &migrate.FileMigrationSource{
+ Dir: cfg.MigrationPath,
+ }
+
+ return Migrator{cfg: cfg, dialect: "mysql", migrations: migrations}
+}
+
+func (m Migrator) Up() {
+ migrate.SetTable(m.cfg.MigrationDBName)
+
+ db, err := sql.Open(m.dialect, fmt.Sprintf("%s:%s@(%s:%d)/%s?parseTime=true",
+ m.cfg.MysqlConfig.Username, m.cfg.MysqlConfig.Password, m.cfg.MysqlConfig.Host, m.cfg.MysqlConfig.Port, m.cfg.MysqlConfig.DBName))
+ if err != nil {
+ panic(fmt.Errorf("can't open mysql db: %w", err))
+ }
+
+ n, err := migrate.Exec(db, m.dialect, m.migrations, migrate.Up)
+ if err != nil {
+ panic(fmt.Errorf("can't apply migrations: %w", err))
+ }
+ fmt.Printf("Applied %d migrations!\n", n)
+}
+
+func (m Migrator) Down() {
+ migrate.SetTable(m.cfg.MigrationDBName)
+
+ db, err := sql.Open(m.dialect, fmt.Sprintf("%s:%s@(%s:%d)/%s?parseTime=true",
+ m.cfg.MysqlConfig.Username, m.cfg.MysqlConfig.Password, m.cfg.MysqlConfig.Host, m.cfg.MysqlConfig.Port, m.cfg.MysqlConfig.DBName))
+ if err != nil {
+ panic(fmt.Errorf("can't open mysql db: %w", err))
+ }
+
+ n, err := migrate.Exec(db, m.dialect, m.migrations, migrate.Down)
+ if err != nil {
+ panic(fmt.Errorf("can't rollback migrations: %w", err))
+ }
+ fmt.Printf("Rollbacked %d migrations!\n", n)
+}
+
+func (m Migrator) Status() {
+ // TODO - add status
+}
diff --git a/pkg/database/mysql/db.go b/pkg/database/mysql/db.go
new file mode 100644
index 00000000..eefe5508
--- /dev/null
+++ b/pkg/database/mysql/db.go
@@ -0,0 +1,90 @@
+package mysql
+
+import (
+ "context"
+ "database/sql"
+ "fmt"
+ "sync"
+ "time"
+
+ querier "git.gocasts.ir/ebhomengo/niki/pkg/query_transaction/sql"
+)
+
+type Config struct {
+ Username string `koanf:"username"`
+ Password string `koanf:"password"`
+ Port int `koanf:"port"`
+ Host string `koanf:"host"`
+ DBName string `koanf:"db_name"`
+}
+
+type DB struct {
+ config Config
+ db *querier.SQLDB
+ mu sync.Mutex
+ statements map[statementKey]*sql.Stmt
+}
+
+func (db *DB) Conn() *querier.SQLDB {
+ return db.db
+}
+
+// TODO: this temporary to ignore linter error (magic number).
+const (
+ dbMaxConnLifetime = time.Minute * 3
+ dbMaxOpenConns = 10
+ dbMaxIdleConns = 10
+)
+
+func New(config Config) *DB {
+ // parseTime=true changes the output type of DATE and DATETIME values to time.Time
+ // instead of []byte / string
+ // The date or datetime like 0000-00-00 00:00:00 is converted into zero value of time.Time
+ db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@(%s:%d)/%s?parseTime=true",
+ config.Username, config.Password, config.Host, config.Port, config.DBName))
+ if err != nil {
+ panic(fmt.Errorf("can't open mysql db: %w", err))
+ }
+
+ // See "Important settings" section.
+ db.SetConnMaxLifetime(dbMaxConnLifetime)
+ db.SetMaxOpenConns(dbMaxOpenConns)
+ db.SetMaxIdleConns(dbMaxIdleConns)
+
+ return &DB{
+ config: config,
+ db: &querier.SQLDB{DB: db},
+ statements: make(map[statementKey]*sql.Stmt),
+ }
+}
+
+func (db *DB) PrepareStatement(ctx context.Context, key statementKey, query string) (*sql.Stmt, error) {
+ db.mu.Lock()
+ defer db.mu.Unlock()
+
+ if stmt, ok := db.statements[key]; ok {
+ return stmt, nil
+ }
+
+ stmt, err := db.db.PrepareContext(ctx, query)
+ if err != nil {
+ return nil, err
+ }
+ db.statements[key] = stmt
+
+ return stmt, nil
+}
+
+func (db *DB) CloseStatements() error {
+ db.mu.Lock()
+ defer db.mu.Unlock()
+
+ for _, stmt := range db.statements {
+ err := stmt.Close()
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
diff --git a/pkg/database/mysql/dbconfig.yml b/pkg/database/mysql/dbconfig.yml
new file mode 100644
index 00000000..ee6c6247
--- /dev/null
+++ b/pkg/database/mysql/dbconfig.yml
@@ -0,0 +1,5 @@
+production:
+ dialect: mysql
+ datasource: niki:nikiappt0lk2o20@(localhost:3306)/niki_db?parseTime=true
+ dir: repository/mysql/migration
+ table: gorp_migrations
diff --git a/pkg/database/mysql/prepared_statement.go b/pkg/database/mysql/prepared_statement.go
new file mode 100644
index 00000000..8ec395ed
--- /dev/null
+++ b/pkg/database/mysql/prepared_statement.go
@@ -0,0 +1,52 @@
+package mysql
+
+type statementKey uint
+
+const (
+ StatementKeyAddressCreateForBenefactor statementKey = iota + 1
+ StatementKeyAddressDeleteForBenefactor
+ StatementKeyAddressIsExistByID
+ StatementKeyAddressGetByID
+ StatementKeyAddressGetAllByBenefactorID
+ StatementKeyAddressUpdate
+ StatementKeyCityGetProvinceIDByID
+ StatementKeyCityIsExistByID
+ StatementKeyCityGetAll
+ StatementKeyProvinceIsExistByID
+ StatementKeyProvinceGetAll
+ StatementKeyAdminAccessControlGetPermissions
+ StatementKeyAdminAdd
+ StatementKeyAdminExistByEmail
+ StatementKeyAdminExistByPhoneNumber
+ StatementKeyAdminAgentExistByID
+ StatementKeyAdminGetByID
+ StatementKeyAdminGetByPhoneNumber
+ StatementKeyAdminAgentGetAll
+ StatementKeyBenefactorGetByID
+ StatementKeyBenefactorGetByIDs
+ StatementKeyBenefactorGetByPhoneNumber
+ StatementKeyBenefactorCreate
+ StatementKeyBenefactorGetAll
+ StatementKeyKindBoxAdd
+ StatementKeyKindBoxAssignReceiverAgent
+ StatementKeyKindBoxEnumerate
+ StatementKeyKindBoxExistForBenefactor
+ StatementKeyKindBoxGetByID
+ StatementKeyKindBoxGetAwaitingReturnByAgent
+ StatementKeyKindBoxRegisterEmptyingRequest
+ StatementKeyKindBoxReturn
+ StatementKeyKindBoxReqAccept
+ StatementKeyKindBoxReqAdd
+ StatementKeyKindBoxReqAssignSenderAgent
+ StatementKeyKindBoxReqDeleteByID
+ StatementKeyKindBoxReqGetByID
+ StatementKeyKindBoxReqGetAwaitingDeliveryByAgent
+ StatementKeyKindBoxReqRollbackToPendingStatus
+ StatementKeyKindBoxReqReject
+ StatementKeyKindBoxReqUpdate
+ StatementKeyKindBoxUpdate
+ StatementKeyReferTimeGetByID
+ StatementKeyReferTimeGetAll
+ StatementKeyBenefactorUpdate
+ StatementKeyBenefactorUpdateStatus
+)
diff --git a/pkg/database/mysql/scanner.go b/pkg/database/mysql/scanner.go
new file mode 100644
index 00000000..0fe80534
--- /dev/null
+++ b/pkg/database/mysql/scanner.go
@@ -0,0 +1,5 @@
+package mysql
+
+type Scanner interface {
+ Scan(dest ...any) error
+}
diff --git a/pkg/database/redis/otp/db.go b/pkg/database/redis/otp/db.go
new file mode 100644
index 00000000..a641cc7c
--- /dev/null
+++ b/pkg/database/redis/otp/db.go
@@ -0,0 +1,11 @@
+package redisotp
+
+import "git.gocasts.ir/ebhomengo/niki/adapter/redis"
+
+type DB struct {
+ adapter *redis.Adapter
+}
+
+func New(adapter *redis.Adapter) *DB {
+ return &DB{adapter: adapter}
+}
diff --git a/pkg/database/redis/otp/delete_code.go b/pkg/database/redis/otp/delete_code.go
new file mode 100644
index 00000000..6fffd6f0
--- /dev/null
+++ b/pkg/database/redis/otp/delete_code.go
@@ -0,0 +1,21 @@
+package redisotp
+
+import (
+ "context"
+
+ richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
+)
+
+func (d DB) DeleteCodeByPhoneNumber(ctx context.Context, phoneNumber string) (bool, error) {
+ const op = "redisotp.GetCodeByPhoneNumber"
+
+ success, err := d.adapter.Client().Del(ctx, phoneNumber).Result()
+ if err != nil {
+ return false, richerror.New(op).WithErr(err).WithKind(richerror.KindUnexpected)
+ }
+ if success != 1 {
+ return false, nil
+ }
+
+ return true, nil
+}
diff --git a/pkg/database/redis/otp/exist_phone_number.go b/pkg/database/redis/otp/exist_phone_number.go
new file mode 100644
index 00000000..d8a0fac2
--- /dev/null
+++ b/pkg/database/redis/otp/exist_phone_number.go
@@ -0,0 +1,21 @@
+package redisotp
+
+import (
+ "context"
+
+ richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
+)
+
+func (d DB) IsExistPhoneNumber(ctx context.Context, phoneNumber string) (bool, error) {
+ const op = "redisotp.IsExistPhoneNumber"
+
+ isExist, err := d.adapter.Client().Exists(ctx, phoneNumber).Result()
+ if err != nil {
+ return false, richerror.New(op).WithErr(err).WithKind(richerror.KindUnexpected)
+ }
+ if isExist == 0 {
+ return false, nil
+ }
+
+ return true, nil
+}
diff --git a/pkg/database/redis/otp/get_code.go b/pkg/database/redis/otp/get_code.go
new file mode 100644
index 00000000..99534e08
--- /dev/null
+++ b/pkg/database/redis/otp/get_code.go
@@ -0,0 +1,25 @@
+package redisotp
+
+import (
+ "context"
+ "errors"
+
+ richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
+ "github.com/redis/go-redis/v9"
+)
+
+func (d DB) GetCodeByPhoneNumber(ctx context.Context, phoneNumber string) (string, error) {
+ const op = "redisotp.GetCodeByPhoneNumber"
+
+ value, err := d.adapter.Client().Get(ctx, phoneNumber).Result()
+ if err != nil {
+ rErr := redis.Nil
+ if errors.As(err, &rErr) {
+ return "", nil
+ }
+
+ return "", richerror.New(op).WithErr(err).WithKind(richerror.KindUnexpected)
+ }
+
+ return value, nil
+}
diff --git a/pkg/database/redis/otp/save_code.go b/pkg/database/redis/otp/save_code.go
new file mode 100644
index 00000000..8674421e
--- /dev/null
+++ b/pkg/database/redis/otp/save_code.go
@@ -0,0 +1,19 @@
+package redisotp
+
+import (
+ "context"
+ "time"
+
+ richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
+)
+
+func (d DB) SaveCodeWithPhoneNumber(ctx context.Context, phoneNumber, code string, expireTime time.Duration) error {
+ const op = "redisotp.SaveCodeWithPhoneNumber"
+
+ err := d.adapter.Client().Set(ctx, phoneNumber, code, expireTime).Err()
+ if err != nil {
+ return richerror.New(op).WithErr(err).WithKind(richerror.KindUnexpected)
+ }
+
+ return nil
+}
diff --git a/pkg/httpserver/constant.go b/pkg/httpserver/constant.go
new file mode 100644
index 00000000..30be3c74
--- /dev/null
+++ b/pkg/httpserver/constant.go
@@ -0,0 +1,7 @@
+package httpserver
+
+import "time"
+
+const (
+ DefaultShutdownTimeout = 10 * time.Second
+)
diff --git a/pkg/httpserver/server.go b/pkg/httpserver/server.go
new file mode 100644
index 00000000..51abd3fe
--- /dev/null
+++ b/pkg/httpserver/server.go
@@ -0,0 +1,121 @@
+package httpserver
+
+import (
+ "context"
+ "fmt"
+ echomiddleware "github.com/gocasters/rankr/pkg/echo_middleware"
+ "github.com/labstack/echo/v4"
+ "github.com/labstack/echo/v4/middleware"
+ "strings"
+ "sync"
+ "time"
+)
+
+type Config struct {
+ Host string `koanf:"host"`
+ Port int `koanf:"port"`
+ CORS CORS `koanf:"cors"`
+ ShutdownTimeout time.Duration `koanf:"shutdown_context_timeout"`
+ HideBanner bool `koanf:"hide_banner"`
+ HidePort bool `koanf:"hide_port"`
+ PublicPaths []string `koanf:"public_paths"`
+
+ // Optional Otel middleware can be injected from outside.
+ OtelMiddleware echo.MiddlewareFunc
+}
+
+type CORS struct {
+ AllowOrigins []string `koanf:"allow_origins"`
+}
+
+type Server struct {
+ router *echo.Echo
+ config *Config
+
+ requireClaimsOnce sync.Once
+}
+
+var basePublicPaths = []string{
+ "/v1/login",
+ "/v1/refresh-token",
+ "/v1/me",
+ "/ping",
+ "/ping-otel",
+}
+
+func New(cfg Config) (*Server, error) {
+ if cfg.Port < 1 || cfg.Port > 65535 {
+ return nil, fmt.Errorf("invalid port: %d", cfg.Port)
+ }
+
+ if cfg.ShutdownTimeout <= 0 {
+ cfg.ShutdownTimeout = DefaultShutdownTimeout
+ }
+
+ e := echo.New()
+
+ if cfg.OtelMiddleware != nil {
+ e.Use(cfg.OtelMiddleware)
+ }
+
+ e.Use(middleware.RequestID())
+ e.Use(middleware.Logger())
+ e.Use(middleware.Recover())
+ e.Use(
+ middleware.CORSWithConfig(
+ middleware.CORSConfig{
+ AllowOrigins: cfg.CORS.AllowOrigins,
+ },
+ ),
+ )
+
+ return &Server{
+ router: e,
+ config: &cfg,
+ }, nil
+}
+
+func (s *Server) GetRouter() *echo.Echo {
+ s.requireClaimsOnce.Do(func() {
+ s.router.Use(
+ echomiddleware.RequireUserInfo(
+ echomiddleware.RequireUserInfoOptions{
+ Skipper: newPublicPathSkipper(s.config.PublicPaths...),
+ },
+ ),
+ )
+ })
+
+ return s.router
+}
+
+func (s *Server) GetConfig() *Config {
+ return s.config
+}
+
+func (s *Server) Start() error {
+ addr := fmt.Sprintf("%s:%d", s.config.Host, s.config.Port)
+
+ s.router.HideBanner = s.config.HideBanner
+ s.router.HidePort = s.config.HidePort
+
+ return s.router.Start(addr)
+}
+
+func (s *Server) Stop(ctx context.Context) error {
+ return s.router.Shutdown(ctx)
+}
+
+func newPublicPathSkipper(extraPaths ...string) middleware.Skipper {
+ paths := make([]string, 0, len(basePublicPaths)+len(extraPaths))
+ paths = append(paths, basePublicPaths...)
+ paths = append(paths, extraPaths...)
+ staticPublicPathSkipper := echomiddleware.SkipExactPaths(paths...)
+
+ return func(c echo.Context) bool {
+ path := strings.TrimSuffix(c.Request().URL.Path, "/")
+ return staticPublicPathSkipper(c) ||
+ strings.HasSuffix(path, "/health-check") ||
+ strings.HasSuffix(path, "/health_check")
+ }
+}
diff --git a/pkg/httpserver/server_test.go b/pkg/httpserver/server_test.go
new file mode 100644
index 00000000..f54ebb37
--- /dev/null
+++ b/pkg/httpserver/server_test.go
@@ -0,0 +1,190 @@
+package httpserver_test
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "testing"
+ "time"
+
+ "github.com/gocasters/rankr/pkg/authhttp"
+ "github.com/gocasters/rankr/pkg/httpserver"
+
+ "github.com/labstack/echo/v4"
+ "github.com/stretchr/testify/assert"
+)
+
+// TestNew tests the constructor function for various scenarios.
+func TestNew(t *testing.T) {
+ t.Run("successful creation with valid config", func(t *testing.T) {
+ // Arrange
+ cfg := httpserver.Config{
+ Port: 8080,
+ ShutdownTimeout: 5 * time.Second,
+ }
+
+ // Act
+ server, err := httpserver.New(cfg)
+
+ // Assert
+ assert.NoError(t, err)
+ assert.NotNil(t, server)
+ assert.NotNil(t, server.GetRouter(), "GetRouter should return a non-nil router")
+ })
+
+ t.Run("error on invalid port", func(t *testing.T) {
+ // Arrange
+ cfg := httpserver.Config{Port: 0} // Invalid port
+
+ // Act
+ server, err := httpserver.New(cfg)
+
+ // Assert
+ assert.Error(t, err)
+ assert.Nil(t, server)
+ assert.Contains(t, err.Error(), "invalid port")
+ })
+
+ t.Run("sets default shutdown timeout", func(t *testing.T) {
+ // Arrange
+ cfg := httpserver.Config{
+ Port: 8080,
+ ShutdownTimeout: 0, // No timeout provided
+ }
+
+ // Act
+ server, err := httpserver.New(cfg)
+
+ // Assert
+ assert.NoError(t, err)
+ assert.NotNil(t, server)
+ assert.Equal(t, httpserver.DefaultShutdownTimeout, server.GetConfig().ShutdownTimeout, "Default timeout should be set")
+ })
+}
+
+// TestOtelMiddlewareInjection verifies that optional middleware is correctly added.
+func TestOtelMiddlewareInjection(t *testing.T) {
+ // Arrange
+ middlewareWasCalled := false
+ mockOtelMiddleware := func(next echo.HandlerFunc) echo.HandlerFunc {
+ return func(c echo.Context) error {
+ middlewareWasCalled = true
+ return next(c)
+ }
+ }
+
+ cfg := httpserver.Config{
+ Port: 8080,
+ OtelMiddleware: mockOtelMiddleware,
+ }
+
+ server, err := httpserver.New(cfg)
+ assert.NoError(t, err)
+
+ server.GetRouter().GET("/test", func(c echo.Context) error {
+ return c.String(http.StatusOK, "ok")
+ })
+
+ // Act
+ req := httptest.NewRequest(http.MethodGet, "/test", nil)
+ userInfo, encErr := authhttp.EncodeUserInfo("1")
+ assert.NoError(t, encErr)
+ req.Header.Set("X-User-Info", userInfo)
+ rec := httptest.NewRecorder()
+ server.GetRouter().ServeHTTP(rec, req)
+
+ // Assert
+ assert.True(t, middlewareWasCalled, "The injected Otel middleware should have been called")
+}
+
+// TestRouteRegistrationAndResponse confirms that routes can be added and respond correctly.
+func TestRouteRegistrationAndResponse(t *testing.T) {
+ // Arrange
+ server, err := httpserver.New(httpserver.Config{Port: 8080})
+ assert.NoError(t, err)
+
+ expectedResponse := "Hello, Tester!"
+
+ // Act: Register a new GET route using the GetRouter() method.
+ server.GetRouter().GET("/hello", func(c echo.Context) error {
+ return c.String(http.StatusOK, expectedResponse)
+ })
+
+ req := httptest.NewRequest(http.MethodGet, "/hello", nil)
+ userInfo, encErr := authhttp.EncodeUserInfo("1")
+ assert.NoError(t, encErr)
+ req.Header.Set("X-User-Info", userInfo)
+ rec := httptest.NewRecorder()
+ server.GetRouter().ServeHTTP(rec, req)
+
+ // Assert
+ assert.Equal(t, http.StatusOK, rec.Code)
+ assert.Equal(t, expectedResponse, rec.Body.String())
+}
+
+func TestStopWithTimeout(t *testing.T) {
+ // Arrange
+ server, err := httpserver.New(httpserver.Config{Port: 9090}) // Use a different port to avoid conflicts
+ assert.NoError(t, err)
+
+ // Act: Start the server in a separate goroutine
+ errCh := make(chan error, 1)
+ go func() {
+ errCh <- server.Start()
+ }()
+
+ // Stop the server and verify shutdown succeeds
+ stopErr := server.Stop(t.Context())
+ assert.NoError(t, stopErr, "StopWithTimeout should not return an error when stopping a running server")
+
+ // Now verify Start() exited due to shutdown
+ startErr := <-errCh
+ assert.ErrorIs(t, startErr, http.ErrServerClosed, "server.Start() should return ErrServerClosed after shutdown")
+}
+
+func TestPublicRoutesAndProtectedRoutes(t *testing.T) {
+ server, err := httpserver.New(httpserver.Config{Port: 8080})
+ assert.NoError(t, err)
+
+ server.GetRouter().GET("/v1/module/health-check", func(c echo.Context) error {
+ return c.String(http.StatusOK, "ok")
+ })
+ server.GetRouter().POST("/v1/login", func(c echo.Context) error {
+ return c.String(http.StatusOK, "ok")
+ })
+ server.GetRouter().GET("/secure", func(c echo.Context) error {
+ return c.String(http.StatusOK, "ok")
+ })
+
+ healthReq := httptest.NewRequest(http.MethodGet, "/v1/module/health-check", nil)
+ healthRec := httptest.NewRecorder()
+ server.GetRouter().ServeHTTP(healthRec, healthReq)
+ assert.Equal(t, http.StatusOK, healthRec.Code)
+
+ loginReq := httptest.NewRequest(http.MethodPost, "/v1/login", nil)
+ loginRec := httptest.NewRecorder()
+ server.GetRouter().ServeHTTP(loginRec, loginReq)
+ assert.Equal(t, http.StatusOK, loginRec.Code)
+
+ secureReq := httptest.NewRequest(http.MethodGet, "/secure", nil)
+ secureRec := httptest.NewRecorder()
+ server.GetRouter().ServeHTTP(secureRec, secureReq)
+ assert.Equal(t, http.StatusUnauthorized, secureRec.Code)
+}
+
+func TestConfigPublicPathsAreSkipped(t *testing.T) {
+ server, err := httpserver.New(httpserver.Config{
+ Port: 8080,
+ PublicPaths: []string{" v1/public/info/ "},
+ })
+ assert.NoError(t, err)
+
+ server.GetRouter().GET("/v1/public/info", func(c echo.Context) error {
+ return c.String(http.StatusOK, "ok")
+ })
+
+ req := httptest.NewRequest(http.MethodGet, "/v1/public/info", nil)
+ rec := httptest.NewRecorder()
+ server.GetRouter().ServeHTTP(rec, req)
+
+ assert.Equal(t, http.StatusOK, rec.Code)
+}
diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go
new file mode 100644
index 00000000..fd87c818
--- /dev/null
+++ b/pkg/logger/logger.go
@@ -0,0 +1,84 @@
+package logger
+
+import (
+ "io"
+ "log"
+ "log/slog"
+ "os"
+ "path/filepath"
+ "sync"
+
+ "gopkg.in/natefinch/lumberjack.v2"
+)
+
+var globalLogger *slog.Logger
+var once sync.Once
+
+type Config struct {
+ Level string `koanf:"level"`
+ FilePath string `koanf:"file_path"`
+ UseLocalTime bool `koanf:"use_local_time"`
+ FileMaxSizeInMB int `koanf:"file_max_size_in_mb"`
+ FileMaxAgeInDays int `koanf:"file_max_age_in_days"`
+}
+
+// Init initializes the global logger instance.
+func Init(cfg Config) {
+ once.Do(func() {
+ workingDir, err := os.Getwd()
+ if err != nil {
+ log.Fatalf("Error getting current working directory: %v", err)
+ }
+ fileWriter := &lumberjack.Logger{
+ Filename: filepath.Join(workingDir, cfg.FilePath),
+ LocalTime: cfg.UseLocalTime,
+ MaxSize: cfg.FileMaxSizeInMB,
+ MaxAge: cfg.FileMaxAgeInDays,
+ }
+
+ level := mapLevel(cfg.Level)
+ globalLogger = slog.New(
+ slog.NewJSONHandler(io.MultiWriter(fileWriter, os.Stdout), &slog.HandlerOptions{
+ Level: level,
+ }),
+ )
+ })
+}
+
+// L returns the global logger instance.
+func L() *slog.Logger {
+ return globalLogger
+}
+
+// New creates a new logger instance for each service with specific settings.
+func New(cfg Config) *slog.Logger {
+ workingDir, err := os.Getwd()
+ if err != nil {
+ log.Fatalf("Error getting current working directory: %v", err)
+ }
+
+ fileWriter := &lumberjack.Logger{
+ Filename: filepath.Join(workingDir, cfg.FilePath),
+ LocalTime: cfg.UseLocalTime,
+ MaxSize: cfg.FileMaxSizeInMB,
+ MaxAge: cfg.FileMaxAgeInDays,
+ }
+ return slog.New(
+ slog.NewJSONHandler(io.MultiWriter(fileWriter), &slog.HandlerOptions{}),
+ )
+}
+
+func mapLevel(levelStr string) slog.Level {
+ switch levelStr {
+ case "debug":
+ return slog.LevelDebug
+ case "info":
+ return slog.LevelInfo
+ case "warn":
+ return slog.LevelWarn
+ case "error":
+ return slog.LevelError
+ default:
+ return slog.LevelInfo
+ }
+}
diff --git a/pkg/path/path.go b/pkg/path/path.go
new file mode 100644
index 00000000..a2bfd1a6
--- /dev/null
+++ b/pkg/path/path.go
@@ -0,0 +1,34 @@
+package path
+
+import (
+ "errors"
+ "os"
+ "path/filepath"
+)
+
+// PathProjectRoot searches upwards from the current directory to find the project root,
+// identified by the presence of a "go.mod" file.
+func PathProjectRoot() (string, error) {
+ dir, err := os.Getwd()
+ if err != nil {
+ return "", err
+ }
+
+ for {
+ if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
+ return dir, nil
+ }
+ if _, err := os.Stat(filepath.Join(dir, "go.work")); err == nil {
+ return dir, nil
+ }
+ if fi, err := os.Stat(filepath.Join(dir, ".git")); err == nil && fi.IsDir() {
+ return dir, nil
+ }
+
+ parent := filepath.Dir(dir)
+ if parent == dir {
+ return "", errors.New("go.mod not found in any parent directory")
+ }
+ dir = parent
+ }
+}
diff --git a/vendor/dario.cat/mergo/.gitignore b/vendor/dario.cat/mergo/.gitignore
index 529c3412..45ad0f1a 100644
--- a/vendor/dario.cat/mergo/.gitignore
+++ b/vendor/dario.cat/mergo/.gitignore
@@ -13,6 +13,9 @@
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
+# Golang/Intellij
+.idea
+
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
.glide/
diff --git a/vendor/dario.cat/mergo/FUNDING.json b/vendor/dario.cat/mergo/FUNDING.json
new file mode 100644
index 00000000..0585e1fe
--- /dev/null
+++ b/vendor/dario.cat/mergo/FUNDING.json
@@ -0,0 +1,7 @@
+{
+ "drips": {
+ "ethereum": {
+ "ownedBy": "0x6160020e7102237aC41bdb156e94401692D76930"
+ }
+ }
+}
diff --git a/vendor/dario.cat/mergo/README.md b/vendor/dario.cat/mergo/README.md
index 7d0cf9f3..0e4a59af 100644
--- a/vendor/dario.cat/mergo/README.md
+++ b/vendor/dario.cat/mergo/README.md
@@ -44,13 +44,21 @@ Also a lovely [comune](http://en.wikipedia.org/wiki/Mergo) (municipality) in the
## Status
-It is ready for production use. [It is used in several projects by Docker, Google, The Linux Foundation, VMWare, Shopify, Microsoft, etc](https://github.com/imdario/mergo#mergo-in-the-wild).
+Mergo is stable and frozen, ready for production. Check a short list of the projects using at large scale it [here](https://github.com/imdario/mergo#mergo-in-the-wild).
+
+No new features are accepted. They will be considered for a future v2 that improves the implementation and fixes bugs for corner cases.
### Important notes
#### 1.0.0
-In [1.0.0](//github.com/imdario/mergo/releases/tag/1.0.0) Mergo moves to a vanity URL `dario.cat/mergo`.
+In [1.0.0](//github.com/imdario/mergo/releases/tag/1.0.0) Mergo moves to a vanity URL `dario.cat/mergo`. No more v1 versions will be released.
+
+If the vanity URL is causing issues in your project due to a dependency pulling Mergo - it isn't a direct dependency in your project - it is recommended to use [replace](https://github.com/golang/go/wiki/Modules#when-should-i-use-the-replace-directive) to pin the version to the last one with the old import URL:
+
+```
+replace github.com/imdario/mergo => github.com/imdario/mergo v0.3.16
+```
#### 0.3.9
@@ -64,55 +72,23 @@ If you were using Mergo before April 6th, 2015, please check your project works
If Mergo is useful to you, consider buying me a coffee, a beer, or making a monthly donation to allow me to keep building great free software. :heart_eyes:
-
### Mergo in the wild
-- [moby/moby](https://github.com/moby/moby)
-- [kubernetes/kubernetes](https://github.com/kubernetes/kubernetes)
-- [vmware/dispatch](https://github.com/vmware/dispatch)
-- [Shopify/themekit](https://github.com/Shopify/themekit)
-- [imdario/zas](https://github.com/imdario/zas)
-- [matcornic/hermes](https://github.com/matcornic/hermes)
-- [OpenBazaar/openbazaar-go](https://github.com/OpenBazaar/openbazaar-go)
-- [kataras/iris](https://github.com/kataras/iris)
-- [michaelsauter/crane](https://github.com/michaelsauter/crane)
-- [go-task/task](https://github.com/go-task/task)
-- [sensu/uchiwa](https://github.com/sensu/uchiwa)
-- [ory/hydra](https://github.com/ory/hydra)
-- [sisatech/vcli](https://github.com/sisatech/vcli)
-- [dairycart/dairycart](https://github.com/dairycart/dairycart)
-- [projectcalico/felix](https://github.com/projectcalico/felix)
-- [resin-os/balena](https://github.com/resin-os/balena)
-- [go-kivik/kivik](https://github.com/go-kivik/kivik)
-- [Telefonica/govice](https://github.com/Telefonica/govice)
-- [supergiant/supergiant](supergiant/supergiant)
-- [SergeyTsalkov/brooce](https://github.com/SergeyTsalkov/brooce)
-- [soniah/dnsmadeeasy](https://github.com/soniah/dnsmadeeasy)
-- [ohsu-comp-bio/funnel](https://github.com/ohsu-comp-bio/funnel)
-- [EagerIO/Stout](https://github.com/EagerIO/Stout)
-- [lynndylanhurley/defsynth-api](https://github.com/lynndylanhurley/defsynth-api)
-- [russross/canvasassignments](https://github.com/russross/canvasassignments)
-- [rdegges/cryptly-api](https://github.com/rdegges/cryptly-api)
-- [casualjim/exeggutor](https://github.com/casualjim/exeggutor)
-- [divshot/gitling](https://github.com/divshot/gitling)
-- [RWJMurphy/gorl](https://github.com/RWJMurphy/gorl)
-- [andrerocker/deploy42](https://github.com/andrerocker/deploy42)
-- [elwinar/rambler](https://github.com/elwinar/rambler)
-- [tmaiaroto/gopartman](https://github.com/tmaiaroto/gopartman)
-- [jfbus/impressionist](https://github.com/jfbus/impressionist)
-- [Jmeyering/zealot](https://github.com/Jmeyering/zealot)
-- [godep-migrator/rigger-host](https://github.com/godep-migrator/rigger-host)
-- [Dronevery/MultiwaySwitch-Go](https://github.com/Dronevery/MultiwaySwitch-Go)
-- [thoas/picfit](https://github.com/thoas/picfit)
-- [mantasmatelis/whooplist-server](https://github.com/mantasmatelis/whooplist-server)
-- [jnuthong/item_search](https://github.com/jnuthong/item_search)
-- [bukalapak/snowboard](https://github.com/bukalapak/snowboard)
-- [containerssh/containerssh](https://github.com/containerssh/containerssh)
-- [goreleaser/goreleaser](https://github.com/goreleaser/goreleaser)
-- [tjpnz/structbot](https://github.com/tjpnz/structbot)
+Mergo is used by [thousands](https://deps.dev/go/dario.cat%2Fmergo/v1.0.0/dependents) [of](https://deps.dev/go/github.com%2Fimdario%2Fmergo/v0.3.16/dependents) [projects](https://deps.dev/go/github.com%2Fimdario%2Fmergo/v0.3.12), including:
+
+* [containerd/containerd](https://github.com/containerd/containerd)
+* [datadog/datadog-agent](https://github.com/datadog/datadog-agent)
+* [docker/cli/](https://github.com/docker/cli/)
+* [goreleaser/goreleaser](https://github.com/goreleaser/goreleaser)
+* [go-micro/go-micro](https://github.com/go-micro/go-micro)
+* [grafana/loki](https://github.com/grafana/loki)
+* [masterminds/sprig](github.com/Masterminds/sprig)
+* [moby/moby](https://github.com/moby/moby)
+* [slackhq/nebula](https://github.com/slackhq/nebula)
+* [volcano-sh/volcano](https://github.com/volcano-sh/volcano)
## Install
@@ -141,6 +117,39 @@ if err := mergo.Merge(&dst, src, mergo.WithOverride); err != nil {
}
```
+If you need to override pointers, so the source pointer's value is assigned to the destination's pointer, you must use `WithoutDereference`:
+
+```go
+package main
+
+import (
+ "fmt"
+
+ "dario.cat/mergo"
+)
+
+type Foo struct {
+ A *string
+ B int64
+}
+
+func main() {
+ first := "first"
+ second := "second"
+ src := Foo{
+ A: &first,
+ B: 2,
+ }
+
+ dest := Foo{
+ A: &second,
+ B: 1,
+ }
+
+ mergo.Merge(&dest, src, mergo.WithOverride, mergo.WithoutDereference)
+}
+```
+
Additionally, you can map a `map[string]interface{}` to a struct (and otherwise, from struct to map), following the same restrictions as in `Merge()`. Keys are capitalized to find each corresponding exported field.
```go
@@ -181,10 +190,6 @@ func main() {
}
```
-Note: if test are failing due missing package, please execute:
-
- go get gopkg.in/yaml.v3
-
### Transformers
Transformers allow to merge specific types differently than in the default behavior. In other words, now you can customize how some types are merged. For example, `time.Time` is a struct; it doesn't have zero value but IsZero can return true because it has fields with zero value. How can we merge a non-zero `time.Time`?
diff --git a/vendor/dario.cat/mergo/SECURITY.md b/vendor/dario.cat/mergo/SECURITY.md
index a5de61f7..3788fcc1 100644
--- a/vendor/dario.cat/mergo/SECURITY.md
+++ b/vendor/dario.cat/mergo/SECURITY.md
@@ -4,8 +4,8 @@
| Version | Supported |
| ------- | ------------------ |
-| 0.3.x | :white_check_mark: |
-| < 0.3 | :x: |
+| 1.x.x | :white_check_mark: |
+| < 1.0 | :x: |
## Security contact information
diff --git a/vendor/dario.cat/mergo/map.go b/vendor/dario.cat/mergo/map.go
index b50d5c2a..759b4f74 100644
--- a/vendor/dario.cat/mergo/map.go
+++ b/vendor/dario.cat/mergo/map.go
@@ -58,7 +58,7 @@ func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int, conf
}
fieldName := field.Name
fieldName = changeInitialCase(fieldName, unicode.ToLower)
- if v, ok := dstMap[fieldName]; !ok || (isEmptyValue(reflect.ValueOf(v), !config.ShouldNotDereference) || overwrite) {
+ if _, ok := dstMap[fieldName]; !ok || (!isEmptyValue(reflect.ValueOf(src.Field(i).Interface()), !config.ShouldNotDereference) && overwrite) || config.overwriteWithEmptyValue {
dstMap[fieldName] = src.Field(i).Interface()
}
}
diff --git a/vendor/dario.cat/mergo/merge.go b/vendor/dario.cat/mergo/merge.go
index 0ef9b213..fd47c95b 100644
--- a/vendor/dario.cat/mergo/merge.go
+++ b/vendor/dario.cat/mergo/merge.go
@@ -269,7 +269,7 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co
if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil {
return
}
- } else {
+ } else if src.Elem().Kind() != reflect.Struct {
if overwriteWithEmptySrc || (overwrite && !src.IsNil()) || dst.IsNil() {
dst.Set(src)
}
diff --git a/vendor/github.com/docker/docker/AUTHORS b/vendor/github.com/docker/docker/AUTHORS
index 5f93eeb4..c7c64947 100644
--- a/vendor/github.com/docker/docker/AUTHORS
+++ b/vendor/github.com/docker/docker/AUTHORS
@@ -2,7 +2,10 @@
# This file lists all contributors to the repository.
# See hack/generate-authors.sh to make modifications.
+17neverends
+7sunarni <710720732@qq.com>
Aanand Prasad
+Aarni Koskela
Aaron Davidson
Aaron Feng
Aaron Hnatiw
@@ -11,6 +14,7 @@ Aaron L. Xu
Aaron Lehmann
Aaron Welch
Aaron Yoshitake
+Abdur Rehman
Abel Muiño
Abhijeet Kasurde
Abhinandan Prativadi
@@ -24,9 +28,11 @@ Adam Avilla
Adam Dobrawy
Adam Eijdenberg
Adam Kunk
+Adam Lamers
Adam Miller
Adam Mills
Adam Pointer
+Adam Simon
Adam Singer
Adam Thornton
Adam Walz
@@ -119,6 +125,7 @@ amangoel
Amen Belayneh
Ameya Gawde
Amir Goldstein
+AmirBuddy
Amit Bakshi
Amit Krishnan
Amit Shukla
@@ -168,6 +175,7 @@ Andrey Kolomentsev
Andrey Petrov
Andrey Stolbovsky
André Martins
+Andrés Maldonado
Andy Chambers
andy diller
Andy Goldstein
@@ -182,6 +190,7 @@ Anes Hasicic
Angel Velazquez
Anil Belur
Anil Madhavapeddy
+Anirudh Aithal
Ankit Jain
Ankush Agarwal
Anonmily
@@ -219,7 +228,8 @@ Artur Meyster
Arun Gupta
Asad Saeeduddin
Asbjørn Enge
-Austin Vazquez
+Ashly Mathew
+Austin Vazquez
averagehuman
Avi Das
Avi Kivity
@@ -285,6 +295,7 @@ Brandon Liu
Brandon Philips
Brandon Rhodes
Brendan Dixon
+Brendon Smith
Brennan Kinney <5098581+polarathene@users.noreply.github.com>
Brent Salisbury
Brett Higgins
@@ -339,12 +350,14 @@ Casey Bisson
Catalin Pirvu
Ce Gao
Cedric Davies
+Cesar Talledo
Cezar Sa Espinola
Chad Swenson
Chance Zibolski
Chander Govindarajan
Chanhun Jeong
Chao Wang
+Charity Kathure
Charles Chan
Charles Hooper
Charles Law
@@ -366,6 +379,7 @@ Chen Qiu
Cheng-mean Liu
Chengfei Shang
Chengguang Xu
+Chengyu Zhu
Chentianze
Chenyang Yan
chenyuzhu
@@ -480,6 +494,7 @@ Daniel Farrell
Daniel Garcia
Daniel Gasienica
Daniel Grunwell
+Daniel Guns
Daniel Helfand
Daniel Hiltgen
Daniel J Walsh
@@ -763,6 +778,7 @@ Frank Macreery
Frank Rosquin
Frank Villaro-Dixon
Frank Yang
+François Scala
Fred Lifton
Frederick F. Kautz IV
Frederico F. de Oliveira
@@ -798,6 +814,7 @@ GennadySpb
Geoff Levand
Geoffrey Bachelet
Geon Kim
+George Adams
George Kontridze
George Ma
George MacRorie
@@ -826,6 +843,7 @@ Gopikannan Venugopalsamy
Gosuke Miyashita
Gou Rao
Govinda Fichtner
+Grace Choi
Grant Millar
Grant Reaber
Graydon Hoare
@@ -966,6 +984,7 @@ James Nugent
James Sanders
James Turnbull
James Watkins-Harvey
+Jameson Hyde
Jamie Hannaford
Jamshid Afshar
Jan Breig
@@ -1064,13 +1083,16 @@ Jim Perrin
Jimmy Cuadra
Jimmy Puckett
Jimmy Song
+jinjiadu
Jinsoo Park
Jintao Zhang
Jiri Appl
Jiri Popelka
Jiuyue Ma
Jiří Župka
+jjimbo137 <115816493+jjimbo137@users.noreply.github.com>
Joakim Roubert
+Joan Grau
Joao Fernandes
Joao Trindade
Joe Beda
@@ -1155,6 +1177,7 @@ Josiah Kiehl
José Tomás Albornoz
Joyce Jang
JP
+JSchltggr
Julian Taylor
Julien Barbier
Julien Bisconti
@@ -1189,6 +1212,7 @@ K. Heller
Kai Blin
Kai Qiang Wu (Kennan)
Kaijie Chen
+Kaita Nakamura
Kamil Domański
Kamjar Gerami
Kanstantsin Shautsou
@@ -1263,6 +1287,7 @@ Krasi Georgiev
Krasimir Georgiev
Kris-Mikael Krister
Kristian Haugene
+Kristian Heljas
Kristina Zabunova
Krystian Wojcicki
Kunal Kushwaha
@@ -1289,6 +1314,7 @@ Laura Brehm
Laura Frank
Laurent Bernaille
Laurent Erignoux
+Laurent Goderre
Laurie Voss
Leandro Motta Barros
Leandro Siqueira
@@ -1369,6 +1395,7 @@ Madhan Raj Mookkandy
Madhav Puri
Madhu Venugopal
Mageee
+maggie44 <64841595+maggie44@users.noreply.github.com>
Mahesh Tiyyagura
malnick
Malte Janduda
@@ -1462,6 +1489,7 @@ Matthias Kühnle
Matthias Rampke
Matthieu Fronton
Matthieu Hauglustaine
+Matthieu MOREL
Mattias Jernberg
Mauricio Garavaglia
mauriyouth
@@ -1579,6 +1607,7 @@ Muayyad Alsadi
Muhammad Zohaib Aslam
Mustafa Akın
Muthukumar R
+Myeongjoon Kim
Máximo Cuadros
Médi-Rémi Hashim
Nace Oroz
@@ -1593,6 +1622,7 @@ Natasha Jarus
Nate Brennand
Nate Eagleson
Nate Jones
+Nathan Baulch
Nathan Carlson
Nathan Herald
Nathan Hsieh
@@ -1655,6 +1685,7 @@ Nuutti Kotivuori
nzwsch
O.S. Tezer
objectified
+Octol1ttle
Odin Ugedal
Oguz Bilgic
Oh Jinkyun
@@ -1689,6 +1720,7 @@ Patrick Hemmer
Patrick St. laurent
Patrick Stapleton
Patrik Cyvoct
+Patrik Leifert
pattichen
Paul "TBBle" Hampson
Paul
@@ -1763,6 +1795,7 @@ Pierre Carrier
Pierre Dal-Pra
Pierre Wacrenier
Pierre-Alain RIVIERE
+pinglanlu
Piotr Bogdan
Piotr Karbowski
Porjo
@@ -1790,6 +1823,7 @@ Quentin Tayssier
r0n22
Rachit Sharma
Radostin Stoyanov
+Rafael Fernández López
Rafal Jeczalik
Rafe Colton
Raghavendra K T
@@ -1845,6 +1879,7 @@ Robert Obryk
Robert Schneider
Robert Shade
Robert Stern
+Robert Sturla
Robert Terhaar
Robert Wallis
Robert Wang
@@ -1856,7 +1891,7 @@ Robin Speekenbrink
Robin Thoni
robpc
Rodolfo Carvalho
-Rodrigo Campos
+Rodrigo Campos