diff --git a/pkg/marketplace/marketplace.go b/pkg/marketplace/marketplace.go new file mode 100644 index 0000000..52b4a8f --- /dev/null +++ b/pkg/marketplace/marketplace.go @@ -0,0 +1,67 @@ +package marketplace + +import ( + "encoding/json" + "fmt" + "strings" +) + +// Module is a marketplace entry pointing to a module's Git repo. +type Module struct { + Code string `json:"code"` + Name string `json:"name"` + Repo string `json:"repo"` + SignKey string `json:"sign_key"` + Category string `json:"category"` +} + +// Index is the root marketplace catalog. +type Index struct { + Version int `json:"version"` + Modules []Module `json:"modules"` + Categories []string `json:"categories"` +} + +// ParseIndex decodes a marketplace index.json. +func ParseIndex(data []byte) (*Index, error) { + var idx Index + if err := json.Unmarshal(data, &idx); err != nil { + return nil, fmt.Errorf("marketplace.ParseIndex: %w", err) + } + return &idx, nil +} + +// Search returns modules matching the query in code, name, or category. +func (idx *Index) Search(query string) []Module { + q := strings.ToLower(query) + var results []Module + for _, m := range idx.Modules { + if strings.Contains(strings.ToLower(m.Code), q) || + strings.Contains(strings.ToLower(m.Name), q) || + strings.Contains(strings.ToLower(m.Category), q) { + results = append(results, m) + } + } + return results +} + +// ByCategory returns all modules in the given category. +func (idx *Index) ByCategory(category string) []Module { + var results []Module + for _, m := range idx.Modules { + if m.Category == category { + results = append(results, m) + } + } + return results +} + +// Find returns the module with the given code, or false if not found. +func (idx *Index) Find(code string) (Module, bool) { + for _, m := range idx.Modules { + if m.Code == code { + return m, true + } + } + return Module{}, false +} diff --git a/pkg/marketplace/marketplace_test.go b/pkg/marketplace/marketplace_test.go new file mode 100644 index 0000000..c51d0ee --- /dev/null +++ b/pkg/marketplace/marketplace_test.go @@ -0,0 +1,65 @@ +package marketplace + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestParseIndex_Good(t *testing.T) { + raw := `{ + "version": 1, + "modules": [ + {"code": "mining-xmrig", "name": "XMRig Miner", "repo": "https://forge.lthn.io/host-uk/mod-xmrig.git", "sign_key": "abc123", "category": "miner"}, + {"code": "utils-cyberchef", "name": "CyberChef", "repo": "https://forge.lthn.io/host-uk/mod-cyberchef.git", "sign_key": "def456", "category": "utils"} + ], + "categories": ["miner", "utils"] + }` + idx, err := ParseIndex([]byte(raw)) + require.NoError(t, err) + assert.Equal(t, 1, idx.Version) + assert.Len(t, idx.Modules, 2) + assert.Equal(t, "mining-xmrig", idx.Modules[0].Code) +} + +func TestSearch_Good(t *testing.T) { + idx := &Index{ + Modules: []Module{ + {Code: "mining-xmrig", Name: "XMRig Miner", Category: "miner"}, + {Code: "utils-cyberchef", Name: "CyberChef", Category: "utils"}, + }, + } + results := idx.Search("miner") + assert.Len(t, results, 1) + assert.Equal(t, "mining-xmrig", results[0].Code) +} + +func TestByCategory_Good(t *testing.T) { + idx := &Index{ + Modules: []Module{ + {Code: "a", Category: "miner"}, + {Code: "b", Category: "utils"}, + {Code: "c", Category: "miner"}, + }, + } + miners := idx.ByCategory("miner") + assert.Len(t, miners, 2) +} + +func TestFind_Good(t *testing.T) { + idx := &Index{ + Modules: []Module{ + {Code: "mining-xmrig", Name: "XMRig"}, + }, + } + m, ok := idx.Find("mining-xmrig") + assert.True(t, ok) + assert.Equal(t, "XMRig", m.Name) +} + +func TestFind_Bad_NotFound(t *testing.T) { + idx := &Index{} + _, ok := idx.Find("nope") + assert.False(t, ok) +}