From 361d36dc389af5cb6757a8e899bfa6a51d4df00c Mon Sep 17 00:00:00 2001 From: Tiago Yamamoto Date: Sun, 14 Dec 2025 15:19:18 -0300 Subject: [PATCH] feat: customize api root response and update dev ingress host - Update root handler to return server public IP via ipify - Update root handler response JSON structure - Update ingress host to api-dev.gohorsejobs.com - Add unit tests for router --- backend/go.mod | 4 +++ backend/go.sum | 3 ++ backend/internal/router/router.go | 39 +++++++++++++++++------ backend/internal/router/router_test.go | 43 ++++++++++++++++++++++++++ k8s/dev/backend-ingress-dev.yaml | 4 +-- 5 files changed, 82 insertions(+), 11 deletions(-) create mode 100644 backend/internal/router/router_test.go diff --git a/backend/go.mod b/backend/go.mod index c53beec..bc85d3d 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -11,6 +11,7 @@ require ( github.com/google/uuid v1.6.0 github.com/joho/godotenv v1.5.1 github.com/lib/pq v1.10.9 + github.com/stretchr/testify v1.7.0 github.com/swaggo/http-swagger/v2 v2.0.2 github.com/swaggo/swag v1.16.6 golang.org/x/crypto v0.45.0 @@ -33,6 +34,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 // indirect github.com/aws/smithy-go v1.24.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-openapi/jsonpointer v0.22.3 // indirect github.com/go-openapi/jsonreference v0.21.3 // indirect github.com/go-openapi/spec v0.22.1 // indirect @@ -43,9 +45,11 @@ require ( github.com/go-openapi/swag/stringutils v0.25.4 // indirect github.com/go-openapi/swag/typeutils v0.25.4 // indirect github.com/go-openapi/swag/yamlutils v0.25.4 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/swaggo/files/v2 v2.0.2 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/mod v0.30.0 // indirect golang.org/x/sync v0.18.0 // indirect golang.org/x/tools v0.39.0 // indirect + gopkg.in/yaml.v3 v3.0.0 // indirect ) diff --git a/backend/go.sum b/backend/go.sum index 83cf5f6..520302a 100755 --- a/backend/go.sum +++ b/backend/go.sum @@ -38,6 +38,7 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 h1:SciGFVNZ4mHdm7gpD1dgZYnCuVdX github.com/aws/aws-sdk-go-v2/service/sts v1.41.5/go.mod h1:iW40X4QBmUxdP+fZNOpfmkdMZqsovezbAeO+Ubiv2pk= github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk= github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-openapi/jsonpointer v0.22.3 h1:dKMwfV4fmt6Ah90zloTbUKWMD+0he+12XYAsPotrkn8= @@ -79,6 +80,7 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/swaggo/files/v2 v2.0.2 h1:Bq4tgS/yxLB/3nwOMcul5oLEUKa877Ykgz3CJMVbQKU= @@ -99,5 +101,6 @@ golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/backend/internal/router/router.go b/backend/internal/router/router.go index a59138b..cfc48c5 100755 --- a/backend/internal/router/router.go +++ b/backend/internal/router/router.go @@ -70,26 +70,47 @@ func NewRouter() http.Handler { cloudflareHandler := cloudflare.NewHandler() cpanelHandler := cpanel.NewHandler() + // cachedPublicIP stores the public IP to avoid repeated external calls + var cachedPublicIP string + + // Helper to get public IP + getPublicIP := func() string { + if cachedPublicIP != "" { + return cachedPublicIP + } + client := http.Client{ + Timeout: 2 * time.Second, + } + resp, err := client.Get("https://api.ipify.org?format=text") + if err != nil { + return "127.0.0.1" // Fallback + } + defer resp.Body.Close() + + // simple read + buf := make([]byte, 64) + n, err := resp.Body.Read(buf) + if err != nil && err.Error() != "EOF" { + return "127.0.0.1" + } + cachedPublicIP = string(buf[:n]) + return cachedPublicIP + } + // --- ROOT ROUTE --- mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/" { http.NotFound(w, r) return } - // Get client IP - clientIP := r.Header.Get("X-Forwarded-For") - if clientIP == "" { - clientIP = r.Header.Get("X-Real-IP") - } - if clientIP == "" { - clientIP = r.RemoteAddr - } + + serverIP := getPublicIP() w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) response := `{ "message": "🐴 GoHorseJobs API is running!", - "ip": "` + clientIP + `", + "ip": "` + serverIP + `", "docs": "/docs", "health": "/health", "version": "1.0.0" diff --git a/backend/internal/router/router_test.go b/backend/internal/router/router_test.go new file mode 100644 index 0000000..f4702a0 --- /dev/null +++ b/backend/internal/router/router_test.go @@ -0,0 +1,43 @@ +package router_test + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/rede5/gohorsejobs/backend/internal/router" + "github.com/stretchr/testify/assert" +) + +func TestRootHandler(t *testing.T) { + // Initialize the router + // Note: NewRouter might try to connect to the DB. + // For this unit test, if NewRouter is tightly coupled to DB, we might face issues. + // However, seeing as we just need to test the / route which doesn't use the DB, + // let's try to run it. If it fails due to DB connection, + // we'll have to mock the dependencies or skip full router initialization for just this handler. + // + // Given the context of the previous file view, NewRouter initializes services that use database.DB. + // If database.DB is nil (which it is here), it might pass fine until a handler ACTUALLY tries to use it. + // The root handler does NOT use the DB, so this might work. + + r := router.NewRouter() + + req, _ := http.NewRequest("GET", "/", nil) + w := httptest.NewRecorder() + + r.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + + var response map[string]interface{} + err := json.Unmarshal(w.Body.Bytes(), &response) + assert.NoError(t, err) + + assert.Equal(t, "🐴 GoHorseJobs API is running!", response["message"]) + assert.NotEmpty(t, response["ip"]) + assert.Equal(t, "/docs", response["docs"]) + assert.Equal(t, "/health", response["health"]) + assert.Equal(t, "1.0.0", response["version"]) +} diff --git a/k8s/dev/backend-ingress-dev.yaml b/k8s/dev/backend-ingress-dev.yaml index 2133e71..00f0853 100644 --- a/k8s/dev/backend-ingress-dev.yaml +++ b/k8s/dev/backend-ingress-dev.yaml @@ -11,7 +11,7 @@ metadata: spec: ingressClassName: traefik rules: - - host: gohorse-backend-dev.gohorsejobs.com + - host: api-dev.gohorsejobs.com http: paths: - path: / @@ -23,5 +23,5 @@ spec: number: 3000 tls: - hosts: - - gohorse-backend-dev.gohorsejobs.com + - api-dev.gohorsejobs.com secretName: gohorse-backend-dev-cert