From ab5a5c7ae2dd869c14189edff3976051981d3739 Mon Sep 17 00:00:00 2001 From: Vantz Stockwell Date: Tue, 17 Mar 2026 06:21:03 -0400 Subject: [PATCH] feat: connection search by name/hostname/tag with json_each filtering Co-Authored-By: Claude Opus 4.6 (1M context) --- internal/connections/search.go | 40 ++++++++++++++++++++++ internal/connections/search_test.go | 53 +++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 internal/connections/search.go create mode 100644 internal/connections/search_test.go diff --git a/internal/connections/search.go b/internal/connections/search.go new file mode 100644 index 0000000..74fd03c --- /dev/null +++ b/internal/connections/search.go @@ -0,0 +1,40 @@ +package connections + +import "fmt" + +func (s *ConnectionService) Search(query string) ([]Connection, error) { + like := "%" + query + "%" + rows, err := s.db.Query( + `SELECT id, name, hostname, port, protocol, group_id, credential_id, + COALESCE(color,''), tags, COALESCE(notes,''), COALESCE(options,'{}'), + sort_order, last_connected, created_at, updated_at + FROM connections + WHERE name LIKE ? COLLATE NOCASE + OR hostname LIKE ? COLLATE NOCASE + OR tags LIKE ? COLLATE NOCASE + OR notes LIKE ? COLLATE NOCASE + ORDER BY last_connected DESC NULLS LAST, name`, + like, like, like, like, + ) + if err != nil { + return nil, fmt.Errorf("search connections: %w", err) + } + defer rows.Close() + return scanConnections(rows) +} + +func (s *ConnectionService) FilterByTag(tag string) ([]Connection, error) { + rows, err := s.db.Query( + `SELECT c.id, c.name, c.hostname, c.port, c.protocol, c.group_id, c.credential_id, + COALESCE(c.color,''), c.tags, COALESCE(c.notes,''), COALESCE(c.options,'{}'), + c.sort_order, c.last_connected, c.created_at, c.updated_at + FROM connections c, json_each(c.tags) AS t + WHERE t.value = ? + ORDER BY c.name`, tag, + ) + if err != nil { + return nil, fmt.Errorf("filter by tag: %w", err) + } + defer rows.Close() + return scanConnections(rows) +} diff --git a/internal/connections/search_test.go b/internal/connections/search_test.go new file mode 100644 index 0000000..b7acf1f --- /dev/null +++ b/internal/connections/search_test.go @@ -0,0 +1,53 @@ +package connections + +import "testing" + +func TestSearchByName(t *testing.T) { + svc := setupTestService(t) + svc.CreateConnection(CreateConnectionInput{Name: "Asgard", Hostname: "192.168.1.4", Port: 22, Protocol: "ssh"}) + svc.CreateConnection(CreateConnectionInput{Name: "Docker", Hostname: "155.254.29.221", Port: 22, Protocol: "ssh"}) + + results, err := svc.Search("asg") + if err != nil { + t.Fatalf("Search() error: %v", err) + } + if len(results) != 1 { + t.Fatalf("len(results) = %d, want 1", len(results)) + } + if results[0].Name != "Asgard" { + t.Errorf("Name = %q, want %q", results[0].Name, "Asgard") + } +} + +func TestSearchByHostname(t *testing.T) { + svc := setupTestService(t) + svc.CreateConnection(CreateConnectionInput{Name: "Asgard", Hostname: "192.168.1.4", Port: 22, Protocol: "ssh"}) + + results, _ := svc.Search("192.168") + if len(results) != 1 { + t.Errorf("len(results) = %d, want 1", len(results)) + } +} + +func TestSearchByTag(t *testing.T) { + svc := setupTestService(t) + svc.CreateConnection(CreateConnectionInput{Name: "ProdServer", Hostname: "10.0.0.1", Port: 22, Protocol: "ssh", Tags: []string{"Prod", "Linux"}}) + svc.CreateConnection(CreateConnectionInput{Name: "DevServer", Hostname: "10.0.0.2", Port: 22, Protocol: "ssh", Tags: []string{"Dev", "Linux"}}) + + results, _ := svc.Search("Prod") + if len(results) != 1 { + t.Errorf("len(results) = %d, want 1", len(results)) + } +} + +func TestFilterByTag(t *testing.T) { + svc := setupTestService(t) + svc.CreateConnection(CreateConnectionInput{Name: "A", Hostname: "10.0.0.1", Port: 22, Protocol: "ssh", Tags: []string{"Prod"}}) + svc.CreateConnection(CreateConnectionInput{Name: "B", Hostname: "10.0.0.2", Port: 22, Protocol: "ssh", Tags: []string{"Dev"}}) + svc.CreateConnection(CreateConnectionInput{Name: "C", Hostname: "10.0.0.3", Port: 22, Protocol: "ssh", Tags: []string{"Prod", "Linux"}}) + + results, _ := svc.FilterByTag("Prod") + if len(results) != 2 { + t.Errorf("len(results) = %d, want 2", len(results)) + } +}