From 38005c0618f8a04478b12d30d814ac9a3f014e53 Mon Sep 17 00:00:00 2001 From: Vassiliy Yegorov Date: Wed, 1 Jul 2026 19:04:46 +0700 Subject: [PATCH] fix(api): add snake_case json tags to Endpoint/Task/request bodies for frontend contract Go's encoding/json does not bridge snake_case <-> PascalCase field names, so store.Endpoint, store.Task and the anonymous request bodies in accounts.go/auth.go were silently decoding empty/zero values from the frontend's snake_case JSON contract (tls_mode, role_label, src_endpoint_id, dst_endpoint_id, src_login/pass, dst_login/pass). Adds explicit json tags; DB layer is unaffected since pgx binds by positional params, not struct-tag reflection. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01MMHQTtnQtQqL8muAXHr9kd --- internal/httpapi/accounts.go | 5 ++++- internal/httpapi/auth.go | 5 ++++- internal/store/endpoints.go | 10 +++++----- internal/store/json_test.go | 35 +++++++++++++++++++++++++++++++++++ internal/store/tasks.go | 12 ++++++------ 5 files changed, 54 insertions(+), 13 deletions(-) create mode 100644 internal/store/json_test.go diff --git a/internal/httpapi/accounts.go b/internal/httpapi/accounts.go index d86a9a6..09b0788 100644 --- a/internal/httpapi/accounts.go +++ b/internal/httpapi/accounts.go @@ -40,7 +40,10 @@ func (s *Server) handleCreateAccount(w http.ResponseWriter, r *http.Request) { return } var body struct { - SrcLogin, SrcPass, DstLogin, DstPass string + SrcLogin string `json:"src_login"` + SrcPass string `json:"src_pass"` + DstLogin string `json:"dst_login"` + DstPass string `json:"dst_pass"` } if err := json.NewDecoder(r.Body).Decode(&body); err != nil { http.Error(w, "bad json", http.StatusBadRequest) diff --git a/internal/httpapi/auth.go b/internal/httpapi/auth.go index 378e3e7..fd25688 100644 --- a/internal/httpapi/auth.go +++ b/internal/httpapi/auth.go @@ -27,7 +27,10 @@ func NewServer(cfg config.Config, s *store.Store, orch *orchestrator.Orchestrato } func (s *Server) handleLogin(w http.ResponseWriter, r *http.Request) { - var body struct{ User, Pass string } + var body struct { + User string `json:"user"` + Pass string `json:"pass"` + } if err := json.NewDecoder(r.Body).Decode(&body); err != nil { http.Error(w, "bad json", http.StatusBadRequest) return diff --git a/internal/store/endpoints.go b/internal/store/endpoints.go index e43a418..aa08f07 100644 --- a/internal/store/endpoints.go +++ b/internal/store/endpoints.go @@ -3,11 +3,11 @@ package store import "context" type Endpoint struct { - ID int64 - RoleLabel string - Host string - Port int - TLSMode string + ID int64 `json:"id"` + RoleLabel string `json:"role_label"` + Host string `json:"host"` + Port int `json:"port"` + TLSMode string `json:"tls_mode"` } func (s *Store) CreateEndpoint(ctx context.Context, e Endpoint) (int64, error) { diff --git a/internal/store/json_test.go b/internal/store/json_test.go new file mode 100644 index 0000000..d050f69 --- /dev/null +++ b/internal/store/json_test.go @@ -0,0 +1,35 @@ +package store + +import ( + "encoding/json" + "strings" + "testing" +) + +func TestEndpointJSONRoundTrip(t *testing.T) { + var e Endpoint + if err := json.Unmarshal([]byte(`{"role_label":"src","host":"h","port":993,"tls_mode":"ssl"}`), &e); err != nil { + t.Fatal(err) + } + if e.RoleLabel != "src" || e.Host != "h" || e.Port != 993 || e.TLSMode != "ssl" { + t.Fatalf("decode failed: %+v", e) + } + b, _ := json.Marshal(e) + if !strings.Contains(string(b), `"tls_mode":"ssl"`) || !strings.Contains(string(b), `"role_label":"src"`) { + t.Fatalf("marshal not snake_case: %s", b) + } +} + +func TestTaskJSONRoundTrip(t *testing.T) { + var tk Task + if err := json.Unmarshal([]byte(`{"name":"n","src_endpoint_id":1,"dst_endpoint_id":2}`), &tk); err != nil { + t.Fatal(err) + } + if tk.Name != "n" || tk.SrcEndpointID != 1 || tk.DstEndpointID != 2 { + t.Fatalf("decode failed: %+v", tk) + } + b, _ := json.Marshal(tk) + if !strings.Contains(string(b), `"src_endpoint_id":1`) { + t.Fatalf("marshal not snake_case: %s", b) + } +} diff --git a/internal/store/tasks.go b/internal/store/tasks.go index 2d48fea..c452c13 100644 --- a/internal/store/tasks.go +++ b/internal/store/tasks.go @@ -3,12 +3,12 @@ package store import "context" type Task struct { - ID int64 - Name string - SrcEndpointID int64 - DstEndpointID int64 - Status string - FolderMapping map[string]string + ID int64 `json:"id"` + Name string `json:"name"` + SrcEndpointID int64 `json:"src_endpoint_id"` + DstEndpointID int64 `json:"dst_endpoint_id"` + Status string `json:"status"` + FolderMapping map[string]string `json:"folder_mapping"` } func (s *Store) CreateTask(ctx context.Context, t Task) (int64, error) {