diff --git a/cmd/main.go b/cmd/main.go index 03ee8d6..dc73741 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -4,6 +4,14 @@ import ( "context" "errors" "fmt" + "net/http" + "os" + "os/signal" + "time" + + "github.com/labstack/echo/v5" + "github.com/samber/do/v2" + "ichi-go/cmd/server" "ichi-go/config" _ "ichi-go/docs" // Import generated docs @@ -12,14 +20,6 @@ import ( "ichi-go/internal/middlewares" errors2 "ichi-go/pkg/errors" "ichi-go/pkg/logger" - "net/http" - "os" - "os/signal" - "time" - - "github.com/samber/do/v2" - - "github.com/labstack/echo/v4" ) // @title Ichi-Go API @@ -72,7 +72,7 @@ func main() { errors2.Setup(e) // Log all routes - for _, route := range e.Routes() { + for _, route := range e.Router().Routes() { if route.Method == "" && route.Path == "" { continue } @@ -90,11 +90,20 @@ func main() { go server.StartQueueWorkers(ctx, msgConfig, msgConn, injector) } - // Start the server + // Start the server with context-based graceful shutdown + serverDone := make(chan struct{}) go func() { + defer close(serverDone) address := fmt.Sprintf(":%d", cfg.Http().Port) logger.Infof("starting http server at %s", address) - if err := e.Start(address); err != nil && !errors.Is(err, http.ErrServerClosed) { + sc := echo.StartConfig{ + Address: address, + GracefulTimeout: 10 * time.Second, + OnShutdownError: func(err error) { + logger.Errorf("error during server shutdown: %v", err) + }, + } + if err := sc.Start(ctx, e); err != nil && !errors.Is(err, http.ErrServerClosed) { logger.Errorf("http server error: %v", err) } }() @@ -104,12 +113,9 @@ func main() { // Graceful shutdown logger.Infof("Received shutdown signal...") - shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - if err := e.Shutdown(shutdownCtx); err != nil { - logger.Fatalf("error during server shutdown: %v", err) - } + // Wait for server graceful shutdown to complete + <-serverDone // Shutdown all services in reverse dependency order logger.Infof("shutting down services...") diff --git a/cmd/server/rest_server.go b/cmd/server/rest_server.go index 46ce267..7d544e5 100644 --- a/cmd/server/rest_server.go +++ b/cmd/server/rest_server.go @@ -4,9 +4,9 @@ import ( "encoding/json" "os" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/samber/do/v2" - echoSwagger "github.com/swaggo/echo-swagger" + echoSwagger "github.com/swaggo/echo-swagger/v2" "ichi-go/config" "ichi-go/internal/applications/auth" @@ -28,8 +28,8 @@ func SetupRestRoutes(injector do.Injector, e *echo.Echo, cfg *config.Config) { // Register application domains user.Register(injector, cfg.App().Name, e, appAuth) auth.Register(injector, cfg.App().Name, e, appAuth) - rbacapp.Register(injector, cfg.App().Name, e, appAuth) // RBAC domain - notificationapp.Register(injector, cfg.App().Name, e, appAuth) // Notification domain + rbacapp.Register(injector, cfg.App().Name, e, appAuth) // RBAC domain + notificationapp.Register(injector, cfg.App().Name, e, appAuth) // Notification domain healthapp.Register(injector, cfg.App().Name, e, cfg) } @@ -38,7 +38,7 @@ func GetServiceName(configApp config.AppConfig) string { } func generateRouteList(e *echo.Echo) { - data, err := json.MarshalIndent(e.Routes(), "", " ") + data, err := json.MarshalIndent(e.Router().Routes(), "", " ") if err != nil { panic(err) } @@ -49,7 +49,6 @@ func generateRouteList(e *echo.Echo) { } func openOpenAPIDocs(e *echo.Echo, cfg *config.Config) { - // Swagger documentation endpoint e.GET("/docs/*", echoSwagger.WrapHandler) logger.Infof("Swagger UI available at http://localhost:%d/docs/index.html", cfg.Http().Port) } diff --git a/cmd/server/web_server.go b/cmd/server/web_server.go index fabd1ba..9219d29 100644 --- a/cmd/server/web_server.go +++ b/cmd/server/web_server.go @@ -3,29 +3,30 @@ package server import ( "html/template" "ichi-go/config" + "io" "net/http" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" ) type TemplateRenderer struct { templates *template.Template } -func (t *TemplateRenderer) Render(w http.ResponseWriter, name string, data interface{}, c echo.Context) error { - return t.templates.ExecuteTemplate(w, name, data) +func (t *TemplateRenderer) Render(c *echo.Context, w io.Writer, templateName string, data any) error { + return t.templates.ExecuteTemplate(w, templateName, data) } func SetupWebRoutes(e *echo.Echo, config *config.Schema) { g := e.Group(config.App.Name) - g.GET("", func(c echo.Context) error { + g.GET("", func(c *echo.Context) error { return c.HTML(http.StatusOK, "Hello, World!") }) //templates := template.Must(template.ParseGlob(filepath.Join("web/templates", "*.html"))) //e.Renderer = &TemplateRenderer{templates} // //e.Static("/static", "web/static") - //e.GET("/", func(c echo.Context) error { + //e.GET("/", func(c *echo.Context) error { // data := map[string]interface{}{ // "title": "Home Page", // "message": "Welcome to the Web Server!", diff --git a/config/config.go b/config/config.go index 97ef141..51ced74 100644 --- a/config/config.go +++ b/config/config.go @@ -13,8 +13,7 @@ import ( "os" "sync" - "github.com/labstack/echo/v4" - "github.com/labstack/gommon/log" + "github.com/labstack/echo/v5" "github.com/spf13/viper" pkgClientConfig "ichi-go/pkg/clients" @@ -212,12 +211,6 @@ func setDefault() { rbac.SetDefault() } -func SetDebugMode(e *echo.Echo, debug bool) { - e.Debug = debug - if debug { - log.SetLevel(log.DEBUG) - } else { - log.SetLevel(log.INFO) - } - log.Debugf("Debug mode set to %v", debug) +func SetDebugMode(_ *echo.Echo, debug bool) { + logger.Debugf("Debug mode set to %v", debug) } diff --git a/go.mod b/go.mod index ec8d241..56490e7 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,6 @@ module ichi-go -go 1.25 - -toolchain go1.25.0 +go 1.25.0 require ( firebase.google.com/go/v4 v4.19.0 @@ -15,8 +13,7 @@ require ( github.com/golang-jwt/jwt/v5 v5.3.0 github.com/google/uuid v1.6.0 github.com/hashicorp/golang-lru/v2 v2.0.7 - github.com/labstack/echo/v4 v4.15.0 - github.com/labstack/gommon v0.4.2 + github.com/labstack/echo/v5 v5.1.0 github.com/pierrec/lz4/v4 v4.1.23 github.com/pressly/goose/v3 v3.26.0 github.com/rabbitmq/amqp091-go v1.10.0 @@ -27,12 +24,12 @@ require ( github.com/samber/oops/loggers/zerolog v0.0.0-20260101195714-ee59589fe6d3 github.com/spf13/viper v1.21.0 github.com/stretchr/testify v1.11.1 - github.com/swaggo/echo-swagger v1.4.1 + github.com/swaggo/echo-swagger/v2 v2.0.1 github.com/swaggo/swag v1.16.6 github.com/uptrace/bun v1.2.16 github.com/uptrace/bun/dialect/mysqldialect v1.2.16 github.com/vmihailenco/msgpack/v5 v5.4.1 - golang.org/x/crypto v0.46.0 + golang.org/x/crypto v0.47.0 google.golang.org/api v0.231.0 resty.dev/v3 v3.0.0-beta.5 ) @@ -65,7 +62,6 @@ require ( github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/gabriel-vasile/mimetype v1.4.12 // indirect - github.com/ghodss/yaml v1.0.0 // indirect github.com/go-jose/go-jose/v4 v4.0.5 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -92,6 +88,7 @@ require ( github.com/mfridman/interpolate v0.0.2 // indirect github.com/oklog/ulid/v2 v2.1.1 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect @@ -106,10 +103,10 @@ require ( github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect + github.com/sv-tools/openapi v0.2.1 // indirect github.com/swaggo/files/v2 v2.0.2 // indirect + github.com/swaggo/swag/v2 v2.0.0-rc4 // indirect github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect - github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasttemplate v1.2.2 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/zeebo/errs v1.4.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect @@ -124,11 +121,11 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/mod v0.31.0 // indirect - golang.org/x/net v0.48.0 // indirect + golang.org/x/net v0.49.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sync v0.19.0 // indirect golang.org/x/sys v0.40.0 // indirect - golang.org/x/text v0.32.0 // indirect + golang.org/x/text v0.33.0 // indirect golang.org/x/time v0.14.0 // indirect golang.org/x/tools v0.40.0 // indirect google.golang.org/appengine/v2 v2.0.6 // indirect @@ -139,4 +136,5 @@ require ( google.golang.org/protobuf v1.36.6 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index 70fa6b0..1cc222c 100644 --- a/go.sum +++ b/go.sum @@ -53,6 +53,7 @@ github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls= github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= @@ -77,8 +78,6 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= -github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -92,7 +91,7 @@ github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmG github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4= github.com/go-openapi/spec v0.22.3 h1:qRSmj6Smz2rEBxMnLRBMeBWxbbOvuOoElvSvObIgwQc= github.com/go-openapi/spec v0.22.3/go.mod h1:iIImLODL2loCh3Vnox8TY2YWYJZjMAKYyLH2Mu8lOZs= -github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= +github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4= github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU= github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= @@ -155,10 +154,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/labstack/echo/v4 v4.15.0 h1:hoRTKWcnR5STXZFe9BmYun9AMTNeSbjHi2vtDuADJ24= -github.com/labstack/echo/v4 v4.15.0/go.mod h1:xmw1clThob0BSVRX1CRQkGQ/vjwcpOMjQZSZa9fKA/c= -github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= -github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= +github.com/labstack/echo/v5 v5.1.0 h1:MvIRydoN+p9cx/zq8Lff6YXqUW2ZaEsOMISzEGSMrBI= +github.com/labstack/echo/v5 v5.1.0/go.mod h1:SyvlSdObGjRXeQfCCXW/sybkZdOOQZBmpKF0bvALaeo= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= @@ -179,6 +176,7 @@ github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0 github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pierrec/lz4/v4 v4.1.23 h1:oJE7T90aYBGtFNrI8+KbETnPymobAhzRrR8Mu8n1yfU= github.com/pierrec/lz4/v4 v4.1.23/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= @@ -231,22 +229,22 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/swaggo/echo-swagger v1.4.1 h1:Yf0uPaJWp1uRtDloZALyLnvdBeoEL5Kc7DtnjzO/TUk= -github.com/swaggo/echo-swagger v1.4.1/go.mod h1:C8bSi+9yH2FLZsnhqMZLIZddpUxZdBYuNHbtaS1Hljc= +github.com/sv-tools/openapi v0.2.1 h1:ES1tMQMJFGibWndMagvdoo34T1Vllxr1Nlm5wz6b1aA= +github.com/sv-tools/openapi v0.2.1/go.mod h1:k5VuZamTw1HuiS9p2Wl5YIDWzYnHG6/FgPOSFXLAhGg= +github.com/swaggo/echo-swagger/v2 v2.0.1 h1:jKR3QiK+ciGjxE0+7qZ/azjtlx/pTVls7pJFJqdJoJI= +github.com/swaggo/echo-swagger/v2 v2.0.1/go.mod h1:BbgiO9XKX6yYU5Rq4ejqVlQI0mVRv6ziFKd0XgdztnQ= github.com/swaggo/files/v2 v2.0.2 h1:Bq4tgS/yxLB/3nwOMcul5oLEUKa877Ykgz3CJMVbQKU= github.com/swaggo/files/v2 v2.0.2/go.mod h1:TVqetIzZsO9OhHX1Am9sRf9LdrFZqoK49N37KON/jr0= github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI= github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg= +github.com/swaggo/swag/v2 v2.0.0-rc4 h1:SZ8cK68gcV6cslwrJMIOqPkJELRwq4gmjvk77MrvHvY= +github.com/swaggo/swag/v2 v2.0.0-rc4/go.mod h1:Ow7Y8gF16BTCDn8YxZbyKn8FkMLRUHekv1kROJZpbvE= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= github.com/uptrace/bun v1.2.16 h1:QlObi6ZIK5Ao7kAALnh91HWYNZUBbVwye52fmlQM9kc= github.com/uptrace/bun v1.2.16/go.mod h1:jMoNg2n56ckaawi/O/J92BHaECmrz6IRjuMWqlMaMTM= github.com/uptrace/bun/dialect/mysqldialect v1.2.16 h1:ok06dAS094cEKvKg38SVAnXMroNHNaM5ZtpRkPE/Oz0= github.com/uptrace/bun/dialect/mysqldialect v1.2.16/go.mod h1:fjbFYeJZCK8z0m0ACvdgs+dbFdDIaLYWDr+jvaPLedQ= -github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= -github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= @@ -282,8 +280,8 @@ go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= -golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -292,8 +290,8 @@ golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= -golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -316,8 +314,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= -golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -360,3 +358,5 @@ modernc.org/sqlite v1.38.2 h1:Aclu7+tgjgcQVShZqim41Bbw9Cho0y/7WzYptXqkEek= modernc.org/sqlite v1.38.2/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E= resty.dev/v3 v3.0.0-beta.5 h1:NV1xbqOLzSq7XMTs1t/HLPvu7xrxoXzF90SR4OO6faQ= resty.dev/v3 v3.0.0-beta.5/go.mod h1:NTOerrC/4T7/FE6tXIZGIysXXBdgNqwMZuKtxpea9NM= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/internal/applications/auth/controller/auth_controller.go b/internal/applications/auth/controller/auth_controller.go index 5b262b8..fcd7165 100644 --- a/internal/applications/auth/controller/auth_controller.go +++ b/internal/applications/auth/controller/auth_controller.go @@ -1,7 +1,6 @@ package auth import ( - "fmt" authDto "ichi-go/internal/applications/auth/dto" authService "ichi-go/internal/applications/auth/service" "ichi-go/pkg/authenticator" @@ -10,7 +9,7 @@ import ( appValidator "ichi-go/pkg/validator" "net/http" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" ) type AuthController struct { @@ -35,7 +34,7 @@ func NewAuthController(service *authService.ServiceImpl) *AuthController { // @Failure 401 {object} response.ErrorResponse "Invalid credentials" // @Failure 500 {object} response.ErrorResponse "Internal server error" // @Router /202601/auth/login [post] -func (c *AuthController) Login(eCtx echo.Context) error { +func (c *AuthController) Login(eCtx *echo.Context) error { var req authDto.LoginRequest // Bind and validate with automatic language detection @@ -66,8 +65,8 @@ func (c *AuthController) Login(eCtx echo.Context) error { // @Success 201 {object} response.SuccessResponse{data=authDto.RegisterResponse} "User registered successfully" // @Failure 400 {object} response.ErrorResponse "Invalid request, validation error, or user already exists" // @Failure 500 {object} response.ErrorResponse "Internal server error" -// @Router /auth/register [post] -func (c *AuthController) Register(eCtx echo.Context) error { +// @Router /202601/auth/register [post] +func (c *AuthController) Register(eCtx *echo.Context) error { var req authDto.RegisterRequest err := appValidator.BindAndValidate(eCtx, &req) @@ -97,8 +96,8 @@ func (c *AuthController) Register(eCtx echo.Context) error { // @Failure 400 {object} response.ErrorResponse "Invalid request or validation error" // @Failure 401 {object} response.ErrorResponse "Invalid or expired refresh token" // @Failure 500 {object} response.ErrorResponse "Internal server error" -// @Router /auth/refresh [post] -func (c *AuthController) RefreshToken(eCtx echo.Context) error { +// @Router /202601/auth/refresh [post] +func (c *AuthController) RefreshToken(eCtx *echo.Context) error { var req authDto.RefreshTokenRequest // Bind and validate with automatic language detection @@ -128,8 +127,8 @@ func (c *AuthController) RefreshToken(eCtx echo.Context) error { // @Security BearerAuth // @Success 200 {object} response.SuccessResponse{data=map[string]string} "Logged out successfully" // @Failure 401 {object} response.ErrorResponse "Unauthorized - invalid or missing token" -// @Router /auth/logout [post] -func (c *AuthController) Logout(eCtx echo.Context) error { +// @Router /202601/auth/logout [post] +func (c *AuthController) Logout(eCtx *echo.Context) error { logger.Infof("User logged out") return response.Success(eCtx, map[string]string{ "message": "Logged out successfully", @@ -147,9 +146,8 @@ func (c *AuthController) Logout(eCtx echo.Context) error { // @Success 200 {object} response.SuccessResponse{data=authDto.UserInfo} "User profile retrieved successfully" // @Failure 401 {object} response.ErrorResponse "Unauthorized - invalid or missing token" // @Failure 500 {object} response.ErrorResponse "Internal server error" -// @Router /auth/me [get] -func (c *AuthController) Me(eCtx echo.Context) error { - fmt.Println("AuthController.Me called") +// @Router /202601/auth/me [get] +func (c *AuthController) Me(eCtx *echo.Context) error { // Get auth context from middleware authCtx, ok := eCtx.Get("auth").(*authenticator.AuthContext) logger.Debugf("AuthController.Me: authCtx=%+v, ok=%v", authCtx, ok) diff --git a/internal/applications/auth/controller/auth_controller_test.go b/internal/applications/auth/controller/auth_controller_test.go index 8a749f5..e6895d7 100644 --- a/internal/applications/auth/controller/auth_controller_test.go +++ b/internal/applications/auth/controller/auth_controller_test.go @@ -13,7 +13,7 @@ import ( "ichi-go/pkg/testutil" "ichi-go/pkg/utils/response" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" diff --git a/internal/applications/auth/controller/auth_routes.go b/internal/applications/auth/controller/auth_routes.go index 55bc195..a8f4b77 100644 --- a/internal/applications/auth/controller/auth_routes.go +++ b/internal/applications/auth/controller/auth_routes.go @@ -4,7 +4,7 @@ import ( "ichi-go/pkg/authenticator" "ichi-go/pkg/versioning" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" ) var Domain = "auth" diff --git a/internal/applications/auth/registry.go b/internal/applications/auth/registry.go index 21aa21b..5b1173d 100644 --- a/internal/applications/auth/registry.go +++ b/internal/applications/auth/registry.go @@ -5,7 +5,7 @@ import ( authController "ichi-go/internal/applications/auth/controller" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/samber/do/v2" ) diff --git a/internal/applications/auth/service/auth_service_test.go b/internal/applications/auth/service/auth_service_test.go index 9d42350..c816bf9 100644 --- a/internal/applications/auth/service/auth_service_test.go +++ b/internal/applications/auth/service/auth_service_test.go @@ -14,7 +14,7 @@ import ( dbModel "ichi-go/pkg/db/model" pkgErrors "ichi-go/pkg/errors" - _ "github.com/labstack/echo/v4" + _ "github.com/labstack/echo/v5" "github.com/samber/oops" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" diff --git a/internal/applications/health/controller/health_controller.go b/internal/applications/health/controller/health_controller.go index 216e017..38de90b 100644 --- a/internal/applications/health/controller/health_controller.go +++ b/internal/applications/health/controller/health_controller.go @@ -6,7 +6,7 @@ import ( "ichi-go/internal/applications/health/service" "ichi-go/pkg/health" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" ) var Domain = "health" @@ -36,7 +36,7 @@ func (c *HealthController) RegisterRoutes(e *echo.Echo, serviceName string) { // @Produce json // @Success 200 {object} health.HealthResponse // @Router /health [get] -func (c *HealthController) GetHealth(ctx echo.Context) error { +func (c *HealthController) GetHealth(ctx *echo.Context) error { response := c.service.GetHealth(ctx.Request().Context()) return ctx.JSON(http.StatusOK, response) } @@ -49,7 +49,7 @@ func (c *HealthController) GetHealth(ctx echo.Context) error { // @Produce json // @Success 200 {object} health.HealthResponse // @Router /health/live [get] -func (c *HealthController) GetLiveness(ctx echo.Context) error { +func (c *HealthController) GetLiveness(ctx *echo.Context) error { response := c.service.GetLiveness(ctx.Request().Context()) return ctx.JSON(http.StatusOK, response) } @@ -63,7 +63,7 @@ func (c *HealthController) GetLiveness(ctx echo.Context) error { // @Success 200 {object} health.HealthResponse "All dependencies healthy" // @Failure 503 {object} health.HealthResponse "One or more dependencies unhealthy" // @Router /health/ready [get] -func (c *HealthController) GetReadiness(ctx echo.Context) error { +func (c *HealthController) GetReadiness(ctx *echo.Context) error { response := c.service.GetReadiness(ctx.Request().Context()) statusCode := http.StatusOK diff --git a/internal/applications/health/registry.go b/internal/applications/health/registry.go index 819a961..030a032 100644 --- a/internal/applications/health/registry.go +++ b/internal/applications/health/registry.go @@ -1,7 +1,7 @@ package health import ( - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/redis/go-redis/v9" "github.com/samber/do/v2" "github.com/uptrace/bun" diff --git a/internal/applications/notification/controller/notification_controller.go b/internal/applications/notification/controller/notification_controller.go index c30e8dc..7b244ec 100644 --- a/internal/applications/notification/controller/notification_controller.go +++ b/internal/applications/notification/controller/notification_controller.go @@ -4,7 +4,7 @@ import ( "net/http" "strings" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "ichi-go/internal/applications/notification/dto" "ichi-go/internal/applications/notification/services" @@ -28,7 +28,7 @@ func NewNotificationController(campaignService *services.CampaignService) *Notif // Returns 201 Created on success with campaign_id, status, and published_at. // Returns 422 Unprocessable Entity when event_slug is not registered or channels are unsupported. // Returns 400 Bad Request for validation failures (missing fields, bad delay, etc.). -func (c *NotificationController) Send(eCtx echo.Context) error { +func (c *NotificationController) Send(eCtx *echo.Context) error { var req dto.SendNotificationRequest if err := eCtx.Bind(&req); err != nil { return response.Error(eCtx, http.StatusBadRequest, err) diff --git a/internal/applications/notification/controller/notification_routes.go b/internal/applications/notification/controller/notification_routes.go index 3d1ec36..0e26fe6 100644 --- a/internal/applications/notification/controller/notification_routes.go +++ b/internal/applications/notification/controller/notification_routes.go @@ -1,7 +1,7 @@ package controller import ( - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "ichi-go/pkg/authenticator" ) diff --git a/internal/applications/notification/registry.go b/internal/applications/notification/registry.go index 6b121d3..8c8af42 100644 --- a/internal/applications/notification/registry.go +++ b/internal/applications/notification/registry.go @@ -1,7 +1,7 @@ package notification import ( - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/samber/do/v2" notifController "ichi-go/internal/applications/notification/controller" diff --git a/internal/applications/rbac/controllers/audit_controller.go b/internal/applications/rbac/controllers/audit_controller.go index 516704a..27e4837 100644 --- a/internal/applications/rbac/controllers/audit_controller.go +++ b/internal/applications/rbac/controllers/audit_controller.go @@ -5,15 +5,17 @@ import ( "net/http" "os" "path/filepath" + "regexp" + "strings" "time" "ichi-go/internal/applications/rbac/dto" "ichi-go/internal/applications/rbac/repositories" "ichi-go/internal/applications/rbac/services" - + "ichi-go/pkg/logger" "ichi-go/pkg/utils/response" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" ) // AuditController handles audit log endpoints @@ -51,7 +53,7 @@ func NewAuditController(auditService *services.AuditService) *AuditController { // @Failure 500 {object} response.ErrorResponse // @Security BearerAuth // @Router /v1/rbac/audit/logs [get] -func (c *AuditController) QueryAuditLogs(ctx echo.Context) error { +func (c *AuditController) QueryAuditLogs(ctx *echo.Context) error { var req dto.AuditQueryRequest if err := ctx.Bind(&req); err != nil { @@ -109,8 +111,8 @@ func (c *AuditController) QueryAuditLogs(ctx echo.Context) error { TenantID: log.TenantID, Decision: log.Decision, DecisionReason: log.DecisionReason, - PolicyBefore: log.PolicyBefore.(map[string]interface{}), - PolicyAfter: log.PolicyAfter.(map[string]interface{}), + PolicyBefore: toMapOrEmpty(log.PolicyBefore), + PolicyAfter: toMapOrEmpty(log.PolicyAfter), Reason: log.Reason, LatencyMs: log.LatencyMs, }) @@ -124,6 +126,47 @@ func (c *AuditController) QueryAuditLogs(ctx echo.Context) error { return response.Success(ctx, resp) } +// toMapOrEmpty casts v to map[string]interface{}. +// - nil → empty map (no policy recorded is expected and normal) +// - correct type → returned as-is +// - unexpected non-nil type → wrapped under "__raw" so the value is +// preserved in the response and a warning is logged for investigation. +func toMapOrEmpty(v interface{}) map[string]interface{} { + if v == nil { + return map[string]interface{}{} + } + if m, ok := v.(map[string]interface{}); ok { + return m + } + logger.Warnf("toMapOrEmpty: unexpected type %T for audit policy field; wrapping as __raw", v) + return map[string]interface{}{"__raw": v} +} + +// safeFileNameRe permits only alphanumerics, dots, underscores, and hyphens. +var safeFileNameRe = regexp.MustCompile(`^[a-zA-Z0-9._-]+$`) + +// sanitizeExportFileName returns a safe filename for export files. If the +// caller-supplied name is empty, absolute, contains path separators, "..", +// or characters outside the allowlist, it falls back to a timestamped default. +// The fallback includes nanosecond precision to avoid collisions between +// concurrent exports. +func sanitizeExportFileName(name, format string) string { + now := time.Now() + fallback := fmt.Sprintf("audit_logs_%s_%09d.%s", now.Format("20060102_150405"), now.Nanosecond(), format) + if name == "" { + return fallback + } + // Strip directory component; reject anything that still looks dangerous. + base := filepath.Base(name) + if filepath.IsAbs(name) || + strings.Contains(name, "..") || + strings.ContainsAny(name, `/\`) || + !safeFileNameRe.MatchString(base) { + return fallback + } + return base +} + // GetAuditStats godoc // // @Summary Get audit statistics @@ -139,7 +182,7 @@ func (c *AuditController) QueryAuditLogs(ctx echo.Context) error { // @Failure 500 {object} response.ErrorResponse // @Security BearerAuth // @Router /v1/rbac/audit/stats [get] -func (c *AuditController) GetAuditStats(ctx echo.Context) error { +func (c *AuditController) GetAuditStats(ctx *echo.Context) error { var req dto.AuditStatsRequest if err := ctx.Bind(&req); err != nil { @@ -196,7 +239,7 @@ func (c *AuditController) GetAuditStats(ctx echo.Context) error { // @Failure 500 {object} response.ErrorResponse // @Security BearerAuth // @Router /v1/rbac/audit/export [post] -func (c *AuditController) ExportAuditLogs(ctx echo.Context) error { +func (c *AuditController) ExportAuditLogs(ctx *echo.Context) error { var req dto.ExportAuditLogsRequest if err := ctx.Bind(&req); err != nil { @@ -219,10 +262,7 @@ func (c *AuditController) ExportAuditLogs(ctx echo.Context) error { } // Generate filename - fileName := req.FileName - if fileName == "" { - fileName = fmt.Sprintf("audit_logs_%s.%s", time.Now().Format("20060102_150405"), req.Format) - } + safeFileName := sanitizeExportFileName(req.FileName, req.Format) // Create export directory exportDir := "./exports/audit" @@ -230,7 +270,7 @@ func (c *AuditController) ExportAuditLogs(ctx echo.Context) error { return response.Error(ctx, http.StatusInternalServerError, err) } - filePath := filepath.Join(exportDir, fileName) + filePath := filepath.Join(exportDir, safeFileName) // Export based on format var err error @@ -277,7 +317,7 @@ func (c *AuditController) ExportAuditLogs(ctx echo.Context) error { // @Failure 500 {object} response.ErrorResponse // @Security BearerAuth // @Router /v1/rbac/audit/mutations [get] -func (c *AuditController) GetRecentMutations(ctx echo.Context) error { +func (c *AuditController) GetRecentMutations(ctx *echo.Context) error { tenantID := ctx.QueryParam("tenant_id") limit := 50 // Default limit @@ -304,8 +344,8 @@ func (c *AuditController) GetRecentMutations(ctx echo.Context) error { ResourceID: log.ResourceID, SubjectID: log.SubjectID, TenantID: log.TenantID, - PolicyBefore: log.PolicyBefore.(map[string]interface{}), - PolicyAfter: log.PolicyAfter.(map[string]interface{}), + PolicyBefore: toMapOrEmpty(log.PolicyBefore), + PolicyAfter: toMapOrEmpty(log.PolicyAfter), Reason: log.Reason, }) } @@ -330,7 +370,7 @@ func (c *AuditController) GetRecentMutations(ctx echo.Context) error { // @Failure 500 {object} response.ErrorResponse // @Security BearerAuth // @Router /v1/rbac/audit/decisions [get] -func (c *AuditController) GetRecentDecisions(ctx echo.Context) error { +func (c *AuditController) GetRecentDecisions(ctx *echo.Context) error { tenantID := ctx.QueryParam("tenant_id") limit := 50 // Default limit diff --git a/internal/applications/rbac/controllers/enforcement_controller.go b/internal/applications/rbac/controllers/enforcement_controller.go index dc1a2ec..55579ee 100644 --- a/internal/applications/rbac/controllers/enforcement_controller.go +++ b/internal/applications/rbac/controllers/enforcement_controller.go @@ -8,7 +8,7 @@ import ( "ichi-go/pkg/requestctx" "ichi-go/pkg/utils/response" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" ) // EnforcementController handles permission check endpoints @@ -36,7 +36,7 @@ func NewEnforcementController(enforcementService *services.EnforcementService) * // @Failure 500 {object} response.ErrorResponse // @Security BearerAuth // @Router /v1/rbac/enforce/check [post] -func (c *EnforcementController) CheckPermission(ctx echo.Context) error { +func (c *EnforcementController) CheckPermission(ctx *echo.Context) error { var req dto.CheckPermissionRequest if err := ctx.Bind(&req); err != nil { @@ -85,7 +85,7 @@ func (c *EnforcementController) CheckPermission(ctx echo.Context) error { // @Failure 500 {object} response.ErrorResponse // @Security BearerAuth // @Router /v1/rbac/enforce/batch [post] -func (c *EnforcementController) CheckBatchPermissions(ctx echo.Context) error { +func (c *EnforcementController) CheckBatchPermissions(ctx *echo.Context) error { var req dto.BatchCheckPermissionRequest if err := ctx.Bind(&req); err != nil { @@ -137,7 +137,7 @@ func (c *EnforcementController) CheckBatchPermissions(ctx echo.Context) error { // @Failure 500 {object} response.ErrorResponse // @Security BearerAuth // @Router /v1/rbac/enforce/my-permissions [get] -func (c *EnforcementController) GetMyPermissions(ctx echo.Context) error { +func (c *EnforcementController) GetMyPermissions(ctx *echo.Context) error { // Get user ID from request context userID := requestctx.GetUserIDAsInt64(ctx.Request().Context()) if userID == 0 { @@ -174,7 +174,7 @@ func (c *EnforcementController) GetMyPermissions(ctx echo.Context) error { // RequirePermission is a helper method for middleware/handlers // It checks permission and returns error if denied -func (c *EnforcementController) RequirePermission(ctx echo.Context, resource, action string) error { +func (c *EnforcementController) RequirePermission(ctx *echo.Context, resource, action string) error { userID := requestctx.GetUserIDAsInt64(ctx.Request().Context()) if userID == 0 { return echo.NewHTTPError(http.StatusUnauthorized, "User not authenticated") diff --git a/internal/applications/rbac/controllers/policy_controller.go b/internal/applications/rbac/controllers/policy_controller.go index 3b42c4a..fefdf7a 100644 --- a/internal/applications/rbac/controllers/policy_controller.go +++ b/internal/applications/rbac/controllers/policy_controller.go @@ -9,7 +9,7 @@ import ( "ichi-go/pkg/utils/response" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" ) // PolicyController handles policy management endpoints @@ -40,7 +40,7 @@ func NewPolicyController(policyService *services.PolicyService) *PolicyControlle // @Failure 500 {object} response.ErrorResponse // @Security BearerAuth // @Router /v1/rbac/policies [get] -func (c *PolicyController) GetPolicies(ctx echo.Context) error { +func (c *PolicyController) GetPolicies(ctx *echo.Context) error { var req dto.GetPoliciesRequest if err := ctx.Bind(&req); err != nil { @@ -75,7 +75,7 @@ func (c *PolicyController) GetPolicies(ctx echo.Context) error { Action: p.V3, }) } - } else if req.Role != nil { + } else if req.Role != nil && req.TenantID != nil { casbinPolicies, err := c.policyService.GetPoliciesByRole(ctx.Request().Context(), *req.Role, *req.TenantID) if err != nil { return response.Error(ctx, http.StatusInternalServerError, err) @@ -127,7 +127,7 @@ func (c *PolicyController) GetPolicies(ctx echo.Context) error { // @Failure 500 {object} response.ErrorResponse // @Security BearerAuth // @Router /v1/rbac/policies [post] -func (c *PolicyController) AddPolicy(ctx echo.Context) error { +func (c *PolicyController) AddPolicy(ctx *echo.Context) error { var req dto.AddPolicyRequest if err := ctx.Bind(&req); err != nil { @@ -175,7 +175,7 @@ func (c *PolicyController) AddPolicy(ctx echo.Context) error { // @Failure 500 {object} response.ErrorResponse // @Security BearerAuth // @Router /v1/rbac/policies [delete] -func (c *PolicyController) RemovePolicy(ctx echo.Context) error { +func (c *PolicyController) RemovePolicy(ctx *echo.Context) error { var req dto.RemovePolicyRequest if err := ctx.Bind(&req); err != nil { @@ -221,7 +221,7 @@ func (c *PolicyController) RemovePolicy(ctx echo.Context) error { // @Failure 500 {object} response.ErrorResponse // @Security BearerAuth // @Router /v1/rbac/policies/count [get] -func (c *PolicyController) GetPolicyCount(ctx echo.Context) error { +func (c *PolicyController) GetPolicyCount(ctx *echo.Context) error { tenantID := ctx.QueryParam("tenant_id") var count int @@ -262,7 +262,7 @@ func (c *PolicyController) GetPolicyCount(ctx echo.Context) error { // @Failure 500 {object} response.ErrorResponse // @Security BearerAuth // @Router /v1/rbac/policies/reload [post] -func (c *PolicyController) ReloadPolicies(ctx echo.Context) error { +func (c *PolicyController) ReloadPolicies(ctx *echo.Context) error { // Get actor ID from context actorID := requestctx.GetUserIDAsInt64(ctx.Request().Context()) if actorID == 0 { diff --git a/internal/applications/rbac/controllers/role_controller.go b/internal/applications/rbac/controllers/role_controller.go index a1a936b..0d16340 100644 --- a/internal/applications/rbac/controllers/role_controller.go +++ b/internal/applications/rbac/controllers/role_controller.go @@ -10,7 +10,7 @@ import ( "ichi-go/pkg/utils/response" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" ) // RoleController handles role management endpoints @@ -41,7 +41,7 @@ func NewRoleController(roleService *services.RoleService) *RoleController { // @Failure 500 {object} response.ErrorResponse // @Security BearerAuth // @Router /v1/rbac/roles [get] -func (c *RoleController) GetRoles(ctx echo.Context) error { +func (c *RoleController) GetRoles(ctx *echo.Context) error { var req dto.GetRolesRequest if err := ctx.Bind(&req); err != nil { @@ -116,7 +116,7 @@ func (c *RoleController) GetRoles(ctx echo.Context) error { // @Failure 404 {object} response.ErrorResponse // @Security BearerAuth // @Router /v1/rbac/roles/{id} [get] -func (c *RoleController) GetRole(ctx echo.Context) error { +func (c *RoleController) GetRole(ctx *echo.Context) error { id, err := strconv.ParseInt(ctx.Param("id"), 10, 64) if err != nil { return response.Error(ctx, http.StatusBadRequest, echo.NewHTTPError(http.StatusBadRequest, "Invalid role ID")) @@ -143,7 +143,7 @@ func (c *RoleController) GetRole(ctx echo.Context) error { // @Failure 404 {object} response.ErrorResponse // @Security BearerAuth // @Router /v1/rbac/roles/{id}/permissions [get] -func (c *RoleController) GetRoleWithPermissions(ctx echo.Context) error { +func (c *RoleController) GetRoleWithPermissions(ctx *echo.Context) error { id, err := strconv.ParseInt(ctx.Param("id"), 10, 64) if err != nil { return response.Error(ctx, http.StatusBadRequest, echo.NewHTTPError(http.StatusBadRequest, "Invalid role ID")) @@ -176,7 +176,7 @@ func (c *RoleController) GetRoleWithPermissions(ctx echo.Context) error { // @Failure 500 {object} response.ErrorResponse // @Security BearerAuth // @Router /v1/rbac/roles [post] -func (c *RoleController) CreateRole(ctx echo.Context) error { +func (c *RoleController) CreateRole(ctx *echo.Context) error { var req dto.CreateRoleRequest if err := ctx.Bind(&req); err != nil { @@ -218,7 +218,7 @@ func (c *RoleController) CreateRole(ctx echo.Context) error { // @Failure 500 {object} response.ErrorResponse // @Security BearerAuth // @Router /v1/rbac/roles/{id} [put] -func (c *RoleController) UpdateRole(ctx echo.Context) error { +func (c *RoleController) UpdateRole(ctx *echo.Context) error { id, err := strconv.ParseInt(ctx.Param("id"), 10, 64) if err != nil { return response.Error(ctx, http.StatusBadRequest, echo.NewHTTPError(http.StatusBadRequest, "Invalid role ID")) @@ -269,7 +269,7 @@ func (c *RoleController) UpdateRole(ctx echo.Context) error { // @Failure 500 {object} response.ErrorResponse // @Security BearerAuth // @Router /v1/rbac/roles/{id} [delete] -func (c *RoleController) DeleteRole(ctx echo.Context) error { +func (c *RoleController) DeleteRole(ctx *echo.Context) error { id, err := strconv.ParseInt(ctx.Param("id"), 10, 64) if err != nil { return response.Error(ctx, http.StatusBadRequest, echo.NewHTTPError(http.StatusBadRequest, "Invalid role ID")) diff --git a/internal/applications/rbac/controllers/user_role_controller.go b/internal/applications/rbac/controllers/user_role_controller.go index 9f525f5..a894875 100644 --- a/internal/applications/rbac/controllers/user_role_controller.go +++ b/internal/applications/rbac/controllers/user_role_controller.go @@ -12,7 +12,7 @@ import ( "ichi-go/pkg/utils/response" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" ) // UserRoleController handles user role assignment endpoints @@ -41,7 +41,7 @@ func NewUserRoleController(userRoleService *services.UserRoleService) *UserRoleC // @Failure 500 {object} response.ErrorResponse // @Security BearerAuth // @Router /v1/rbac/users/{userId}/roles [get] -func (c *UserRoleController) GetUserRoles(ctx echo.Context) error { +func (c *UserRoleController) GetUserRoles(ctx *echo.Context) error { userID, err := strconv.ParseInt(ctx.Param("userId"), 10, 64) if err != nil { return response.Error(ctx, http.StatusBadRequest, echo.NewHTTPError(http.StatusBadRequest, "Invalid user ID")) @@ -90,7 +90,7 @@ func (c *UserRoleController) GetUserRoles(ctx echo.Context) error { // @Failure 500 {object} response.ErrorResponse // @Security BearerAuth // @Router /v1/rbac/users/{userId}/roles/active [get] -func (c *UserRoleController) GetActiveUserRoles(ctx echo.Context) error { +func (c *UserRoleController) GetActiveUserRoles(ctx *echo.Context) error { userID, err := strconv.ParseInt(ctx.Param("userId"), 10, 64) if err != nil { return response.Error(ctx, http.StatusBadRequest, echo.NewHTTPError(http.StatusBadRequest, "Invalid user ID")) @@ -140,7 +140,7 @@ func (c *UserRoleController) GetActiveUserRoles(ctx echo.Context) error { // @Failure 500 {object} response.ErrorResponse // @Security BearerAuth // @Router /v1/rbac/users/{userId}/roles [post] -func (c *UserRoleController) AssignRole(ctx echo.Context) error { +func (c *UserRoleController) AssignRole(ctx *echo.Context) error { userID, err := strconv.ParseInt(ctx.Param("userId"), 10, 64) if err != nil { return response.Error(ctx, http.StatusBadRequest, echo.NewHTTPError(http.StatusBadRequest, "Invalid user ID")) @@ -197,7 +197,7 @@ func (c *UserRoleController) AssignRole(ctx echo.Context) error { // @Failure 500 {object} response.ErrorResponse // @Security BearerAuth // @Router /v1/rbac/users/{userId}/roles/{roleSlug} [delete] -func (c *UserRoleController) RevokeRole(ctx echo.Context) error { +func (c *UserRoleController) RevokeRole(ctx *echo.Context) error { userID, err := strconv.ParseInt(ctx.Param("userId"), 10, 64) if err != nil { return response.Error(ctx, http.StatusBadRequest, echo.NewHTTPError(http.StatusBadRequest, "Invalid user ID")) @@ -263,7 +263,7 @@ func (c *UserRoleController) RevokeRole(ctx echo.Context) error { // @Failure 500 {object} response.ErrorResponse // @Security BearerAuth // @Router /v1/rbac/roles/{roleId}/users [get] -func (c *UserRoleController) GetUsersWithRole(ctx echo.Context) error { +func (c *UserRoleController) GetUsersWithRole(ctx *echo.Context) error { roleID, err := strconv.ParseInt(ctx.Param("roleId"), 10, 64) if err != nil { return response.Error(ctx, http.StatusBadRequest, echo.NewHTTPError(http.StatusBadRequest, "Invalid role ID")) diff --git a/internal/applications/rbac/registry.go b/internal/applications/rbac/registry.go index c6114a3..b6c2aca 100644 --- a/internal/applications/rbac/registry.go +++ b/internal/applications/rbac/registry.go @@ -4,7 +4,7 @@ import ( "ichi-go/internal/applications/rbac/controllers" "ichi-go/pkg/authenticator" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/samber/do/v2" ) diff --git a/internal/applications/user/controller/user_controller.go b/internal/applications/user/controller/user_controller.go index c6474f1..0612b3c 100644 --- a/internal/applications/user/controller/user_controller.go +++ b/internal/applications/user/controller/user_controller.go @@ -13,7 +13,7 @@ import ( "strconv" dtoMapper "github.com/dranikpg/dto-mapper" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" ) type UserController struct { @@ -24,7 +24,7 @@ func NewUserController(service *userService.ServiceImpl) *UserController { return &UserController{service: service} } -func (c *UserController) GetUser(eCtx echo.Context) error { +func (c *UserController) GetUser(eCtx *echo.Context) error { var userGetReq userDto.UserGetRequest logger.Debugf("GetUser request: %+v", requestctx.FromContext(eCtx.Request().Context())) err := eCtx.Bind(&userGetReq) @@ -32,8 +32,11 @@ func (c *UserController) GetUser(eCtx echo.Context) error { return response.Error(eCtx, http.StatusBadRequest, err) } - idString, err := strconv.Atoi(userGetReq.ID) - user, err := c.service.GetById(eCtx.Request().Context(), uint32(idString)) + idInt, err := strconv.Atoi(userGetReq.ID) + if err != nil { + return response.Error(eCtx, http.StatusBadRequest, err) + } + user, err := c.service.GetById(eCtx.Request().Context(), uint32(idInt)) if err != nil { return response.Error(eCtx, http.StatusBadRequest, err) } @@ -47,7 +50,7 @@ func (c *UserController) GetUser(eCtx echo.Context) error { return response.Success(eCtx, user) } -func (c *UserController) CreateUser(eCtx echo.Context) error { +func (c *UserController) CreateUser(eCtx *echo.Context) error { var userCreateReq userDto.UserCreateRequest err := eCtx.Bind(&userCreateReq) if err != nil { @@ -67,7 +70,7 @@ func (c *UserController) CreateUser(eCtx echo.Context) error { return response.Created(eCtx, map[string]interface{}{"new_user_id": user}) } -func (c *UserController) UpdateUser(eCtx echo.Context) error { +func (c *UserController) UpdateUser(eCtx *echo.Context) error { var userUpdateReq userDto.UserUpdateRequest err := eCtx.Bind(&userUpdateReq) if err != nil { @@ -87,17 +90,19 @@ func (c *UserController) UpdateUser(eCtx echo.Context) error { return response.Success(eCtx, user) } -func (c *UserController) GetUserPage(eCtx echo.Context) error { +func (c *UserController) GetUserPage(eCtx *echo.Context) error { return eCtx.HTML(http.StatusOK, "