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
This commit is contained in:
Tiago Yamamoto 2025-12-14 15:19:18 -03:00
parent 12199fb300
commit 361d36dc38
5 changed files with 82 additions and 11 deletions

View file

@ -11,6 +11,7 @@ require (
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/joho/godotenv v1.5.1 github.com/joho/godotenv v1.5.1
github.com/lib/pq v1.10.9 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/http-swagger/v2 v2.0.2
github.com/swaggo/swag v1.16.6 github.com/swaggo/swag v1.16.6
golang.org/x/crypto v0.45.0 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/ssooidc v1.35.12 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 // 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/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/jsonpointer v0.22.3 // indirect
github.com/go-openapi/jsonreference v0.21.3 // indirect github.com/go-openapi/jsonreference v0.21.3 // indirect
github.com/go-openapi/spec v0.22.1 // 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/stringutils v0.25.4 // indirect
github.com/go-openapi/swag/typeutils 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/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 github.com/swaggo/files/v2 v2.0.2 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/mod v0.30.0 // indirect golang.org/x/mod v0.30.0 // indirect
golang.org/x/sync v0.18.0 // indirect golang.org/x/sync v0.18.0 // indirect
golang.org/x/tools v0.39.0 // indirect golang.org/x/tools v0.39.0 // indirect
gopkg.in/yaml.v3 v3.0.0 // indirect
) )

View file

@ -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/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 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk=
github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= 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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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= 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/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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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= 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= 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 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -70,26 +70,47 @@ func NewRouter() http.Handler {
cloudflareHandler := cloudflare.NewHandler() cloudflareHandler := cloudflare.NewHandler()
cpanelHandler := cpanel.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 --- // --- ROOT ROUTE ---
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" { if r.URL.Path != "/" {
http.NotFound(w, r) http.NotFound(w, r)
return return
} }
// Get client IP
clientIP := r.Header.Get("X-Forwarded-For") serverIP := getPublicIP()
if clientIP == "" {
clientIP = r.Header.Get("X-Real-IP")
}
if clientIP == "" {
clientIP = r.RemoteAddr
}
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
response := `{ response := `{
"message": "🐴 GoHorseJobs API is running!", "message": "🐴 GoHorseJobs API is running!",
"ip": "` + clientIP + `", "ip": "` + serverIP + `",
"docs": "/docs", "docs": "/docs",
"health": "/health", "health": "/health",
"version": "1.0.0" "version": "1.0.0"

View file

@ -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"])
}

View file

@ -11,7 +11,7 @@ metadata:
spec: spec:
ingressClassName: traefik ingressClassName: traefik
rules: rules:
- host: gohorse-backend-dev.gohorsejobs.com - host: api-dev.gohorsejobs.com
http: http:
paths: paths:
- path: / - path: /
@ -23,5 +23,5 @@ spec:
number: 3000 number: 3000
tls: tls:
- hosts: - hosts:
- gohorse-backend-dev.gohorsejobs.com - api-dev.gohorsejobs.com
secretName: gohorse-backend-dev-cert secretName: gohorse-backend-dev-cert