From a17f6dff0ba4eb2a8716c37a74d5b103892a7eee Mon Sep 17 00:00:00 2001 From: Sreeram Narayanan Date: Mon, 28 Oct 2024 20:22:26 +0530 Subject: [PATCH 01/26] add: init project files --- .gitignore | 3 +++ go.mod | 3 +++ main.go | 5 +++++ 3 files changed, 11 insertions(+) create mode 100644 .gitignore create mode 100644 go.mod create mode 100644 main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..874cb3137 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea +main + diff --git a/go.mod b/go.mod new file mode 100644 index 000000000..ee6c779c5 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/sreeram77/challenge2016 + +go 1.23.1 diff --git a/main.go b/main.go new file mode 100644 index 000000000..790580777 --- /dev/null +++ b/main.go @@ -0,0 +1,5 @@ +package main + +func main() { + +} From 30ef7cd30c26ad66a2cfecd76375511524f36ba3 Mon Sep 17 00:00:00 2001 From: Sreeram Narayanan Date: Mon, 28 Oct 2024 20:35:57 +0530 Subject: [PATCH 02/26] add: Region and Distributor models --- models/distributor.go | 23 +++++++++++++++++++++++ models/region.go | 22 ++++++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 models/distributor.go create mode 100644 models/region.go diff --git a/models/distributor.go b/models/distributor.go new file mode 100644 index 000000000..46034e4d0 --- /dev/null +++ b/models/distributor.go @@ -0,0 +1,23 @@ +package models + +type Distributor struct { + Name string + IncludeRegions []Region + ExcludedRegions []Region +} + +func (d Distributor) String() string { + res := "Name: " + d.Name + "\n" + + res += "INCLUDE:" + for _, region := range d.IncludeRegions { + res += region.String() + "\n" + } + + res += "EXCLUDE: " + for _, region := range d.ExcludedRegions { + res += region.String() + "\n" + } + + return res +} diff --git a/models/region.go b/models/region.go new file mode 100644 index 000000000..11602a7bc --- /dev/null +++ b/models/region.go @@ -0,0 +1,22 @@ +package models + +type Region struct { + CountryCode string `csv:"Country Code"` + ProvinceCode string `csv:"Province Code"` + CityCode string `csv:"City Code"` +} + +func (r Region) String() string { + res := "" + + for i, v := range []string{r.CountryCode, r.ProvinceCode, r.CityCode} { + if v != "" { + if i != 0 { + res += "-" + } + res += v + } + } + + return res +} From 0130b08ab24919d73b5d24dddcc7406b303f88fe Mon Sep 17 00:00:00 2001 From: Sreeram Narayanan Date: Mon, 28 Oct 2024 20:48:20 +0530 Subject: [PATCH 03/26] add: countries model to store geography --- go.mod | 2 +- models/geography.go | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 models/geography.go diff --git a/go.mod b/go.mod index ee6c779c5..8e4ec9e65 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ -module github.com/sreeram77/challenge2016 +module challenge2016 go 1.23.1 diff --git a/models/geography.go b/models/geography.go new file mode 100644 index 000000000..b01a6167f --- /dev/null +++ b/models/geography.go @@ -0,0 +1,19 @@ +package models + +type Countries map[string]Country + +type Country struct { + Name string + Code string + Provinces Provinces +} + +type Provinces map[string]Province + +type Province struct { + Name string + Code string + Cities Cities +} + +type Cities map[string]string From d6fc519f103a67708745f5b213a7c08544e61507 Mon Sep 17 00:00:00 2001 From: Sreeram Narayanan Date: Mon, 28 Oct 2024 21:22:40 +0530 Subject: [PATCH 04/26] add: csv package to parse file --- csv/load.go | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 csv/load.go diff --git a/csv/load.go b/csv/load.go new file mode 100644 index 000000000..1045bd208 --- /dev/null +++ b/csv/load.go @@ -0,0 +1,88 @@ +package csv + +import ( + "encoding/csv" + "errors" + "os" + + "challenge2016/models" +) + +// Load reads a CSV file and returns a map of countries. +func Load(file string) (models.Countries, error) { + // Load content from file. + content, err := os.Open(file) + if err != nil { + return nil, err + } + + // Parse content as CSV. + reader := csv.NewReader(content) + if reader == nil { + return nil, errors.New("could not read CSV") + } + + records, err := reader.ReadAll() + if err != nil { + return nil, err + } + + countries := make(models.Countries) + + for i, record := range records { + // Skip the first row + if i == 0 { + continue + } + + country, exists := countries[record[2]] + if !exists { + cities := make(models.Cities) + cities[record[0]] = record[3] + + province := models.Province{ + Name: record[4], + Code: record[1], + Cities: cities, + } + + provinces := make(models.Provinces) + provinces[record[1]] = province + + country = models.Country{ + Name: record[5], + Code: record[2], + Provinces: provinces, + } + + countries[record[2]] = country + + continue + } + + province, exists := country.Provinces[record[1]] + if !exists { + cities := make(models.Cities) + cities[record[0]] = record[3] + + province := models.Province{ + Name: record[4], + Code: record[1], + Cities: cities, + } + + country.Provinces[record[1]] = province + + continue + } + + _, exists = province.Cities[record[0]] + if !exists { + province.Cities[record[0]] = record[3] + } + + province.Cities[record[0]] = record[3] + } + + return countries, nil +} From 08ac768eeb7f24fee3c733c87f4ebd090883f4f6 Mon Sep 17 00:00:00 2001 From: Sreeram Narayanan Date: Mon, 28 Oct 2024 21:32:06 +0530 Subject: [PATCH 05/26] add: Distributors store interface --- store/store.go | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 store/store.go diff --git a/store/store.go b/store/store.go new file mode 100644 index 000000000..bc77fe9f5 --- /dev/null +++ b/store/store.go @@ -0,0 +1,9 @@ +package store + +import "challenge2016/models" + +type Distributors interface { + Insert(models.Distributor) error + Exists(name string) (bool, error) + CheckPermission(distributor string, region models.Region) (bool, error) +} From 9baf2b7d4eabc67b271b39f444033431612080b3 Mon Sep 17 00:00:00 2001 From: Sreeram Narayanan Date: Mon, 28 Oct 2024 21:33:26 +0530 Subject: [PATCH 06/26] update: add Parent in models.Distributor --- models/distributor.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/models/distributor.go b/models/distributor.go index 46034e4d0..c5794def8 100644 --- a/models/distributor.go +++ b/models/distributor.go @@ -4,8 +4,20 @@ type Distributor struct { Name string IncludeRegions []Region ExcludedRegions []Region + Parent *Distributor } +// NewDistributor returns a new distributor. +func NewDistributor(name string, includeRegions []Region, excludedRegions []Region, parent *Distributor) *Distributor { + return &Distributor{ + Name: name, + IncludeRegions: includeRegions, + ExcludedRegions: excludedRegions, + Parent: parent, + } +} + +// String returns a string representation of the distributor. func (d Distributor) String() string { res := "Name: " + d.Name + "\n" From 3e68fb38822b128a8eb0406f7d3882c37323dc43 Mon Sep 17 00:00:00 2001 From: Sreeram Narayanan Date: Tue, 29 Oct 2024 13:50:19 +0530 Subject: [PATCH 07/26] add: Parse to parse Region from string --- models/region.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/models/region.go b/models/region.go index 11602a7bc..ba6929fa8 100644 --- a/models/region.go +++ b/models/region.go @@ -1,5 +1,7 @@ package models +import "strings" + type Region struct { CountryCode string `csv:"Country Code"` ProvinceCode string `csv:"Province Code"` @@ -20,3 +22,22 @@ func (r Region) String() string { return res } + +func Parse(region string) Region { + split := strings.Split(region, "-") + + var r Region + + switch len(split) { + case 1: + r.CountryCode = split[0] + fallthrough + case 2: + r.ProvinceCode = split[1] + fallthrough + case 3: + r.CityCode = split[2] + } + + return r +} From 5c00377d367539974bfd932c4402048b3bd2d6df Mon Sep 17 00:00:00 2001 From: Sreeram Narayanan Date: Tue, 29 Oct 2024 14:36:46 +0530 Subject: [PATCH 08/26] update: add ValidateRegion in Distributor --- models/distributor.go | 52 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/models/distributor.go b/models/distributor.go index c5794def8..3f6576cee 100644 --- a/models/distributor.go +++ b/models/distributor.go @@ -8,12 +8,11 @@ type Distributor struct { } // NewDistributor returns a new distributor. -func NewDistributor(name string, includeRegions []Region, excludedRegions []Region, parent *Distributor) *Distributor { - return &Distributor{ +func NewDistributor(name string, includeRegions []Region, excludedRegions []Region) Distributor { + return Distributor{ Name: name, IncludeRegions: includeRegions, ExcludedRegions: excludedRegions, - Parent: parent, } } @@ -33,3 +32,50 @@ func (d Distributor) String() string { return res } + +// ValidateRegion returns true if the region is authorized for the distributor. +func (d Distributor) ValidateRegion(region Region) bool { + result := validateRegion(region, d.ExcludedRegions) + if result { + return false + } + + result = validateRegion(region, d.IncludeRegions) + if result { + return true + } + + return false +} + +func validateRegion(region Region, regions []Region) bool { + for _, r := range regions { + // Exact match. + if r == region { + return true + } + + if r.CountryCode != region.CountryCode { + continue + } + + if r.ProvinceCode == "" { + return true + } + + if r.ProvinceCode != region.ProvinceCode { + continue + } + + if r.CityCode == "" { + return true + } + + if r.CityCode != region.CityCode { + continue + } + + return true + } + return false +} From cf7292c9f90383614b1ee79972270a7d48ad68bc Mon Sep 17 00:00:00 2001 From: Sreeram Narayanan Date: Tue, 29 Oct 2024 14:37:32 +0530 Subject: [PATCH 09/26] fix: Region parse failing to parse only CountryCode --- models/region.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/models/region.go b/models/region.go index ba6929fa8..bc04c791c 100644 --- a/models/region.go +++ b/models/region.go @@ -31,11 +31,12 @@ func Parse(region string) Region { switch len(split) { case 1: r.CountryCode = split[0] - fallthrough case 2: + r.CountryCode = split[0] r.ProvinceCode = split[1] - fallthrough case 3: + r.CountryCode = split[0] + r.ProvinceCode = split[1] r.CityCode = split[2] } From 22585676689469855741a8a6ed1208438b047685 Mon Sep 17 00:00:00 2001 From: Sreeram Narayanan Date: Tue, 29 Oct 2024 14:37:53 +0530 Subject: [PATCH 10/26] refactor: countries to geography --- csv/load.go | 4 ++-- models/geography.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/csv/load.go b/csv/load.go index 1045bd208..b3d6723d6 100644 --- a/csv/load.go +++ b/csv/load.go @@ -9,7 +9,7 @@ import ( ) // Load reads a CSV file and returns a map of countries. -func Load(file string) (models.Countries, error) { +func Load(file string) (models.Geography, error) { // Load content from file. content, err := os.Open(file) if err != nil { @@ -27,7 +27,7 @@ func Load(file string) (models.Countries, error) { return nil, err } - countries := make(models.Countries) + countries := make(models.Geography) for i, record := range records { // Skip the first row diff --git a/models/geography.go b/models/geography.go index b01a6167f..49f0c2288 100644 --- a/models/geography.go +++ b/models/geography.go @@ -1,6 +1,6 @@ package models -type Countries map[string]Country +type Geography map[string]Country type Country struct { Name string From 3ef6473c8b065be189f6512bf35f753865edb595 Mon Sep 17 00:00:00 2001 From: Sreeram Narayanan Date: Tue, 29 Oct 2024 14:38:41 +0530 Subject: [PATCH 11/26] update: Distributors interface signature --- store/store.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/store/store.go b/store/store.go index bc77fe9f5..10a85abd6 100644 --- a/store/store.go +++ b/store/store.go @@ -3,7 +3,7 @@ package store import "challenge2016/models" type Distributors interface { - Insert(models.Distributor) error - Exists(name string) (bool, error) - CheckPermission(distributor string, region models.Region) (bool, error) + Insert(distributor models.Distributor, parentName string) error + Exists(name string) bool + CheckAuthorization(distributor string, region models.Region) (bool, error) } From f0991de0b526cb5a7e3c8ca626f7369c7e82a4e8 Mon Sep 17 00:00:00 2001 From: Sreeram Narayanan Date: Tue, 29 Oct 2024 14:43:39 +0530 Subject: [PATCH 12/26] update: ValidateIncludedRegions and ValidateExcludedRegions in Distributor --- models/distributor.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/models/distributor.go b/models/distributor.go index 3f6576cee..67f8ba061 100644 --- a/models/distributor.go +++ b/models/distributor.go @@ -48,6 +48,16 @@ func (d Distributor) ValidateRegion(region Region) bool { return false } +// ValidateIncludedRegions returns true if the region is authorized for the distributor. +func (d Distributor) ValidateIncludedRegions(region Region) bool { + return validateRegion(region, d.IncludeRegions) +} + +// ValidateExcludedRegions returns true if the region is authorized for the distributor. +func (d Distributor) ValidateExcludedRegions(region Region) bool { + return validateRegion(region, d.ExcludedRegions) +} + func validateRegion(region Region, regions []Region) bool { for _, r := range regions { // Exact match. From ca093b70ab558aa32caf54b0a0c8ca7c7f1fa480 Mon Sep 17 00:00:00 2001 From: Sreeram Narayanan Date: Tue, 29 Oct 2024 14:44:14 +0530 Subject: [PATCH 13/26] add: implement Distributors store --- store/distributor.go | 84 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 store/distributor.go diff --git a/store/distributor.go b/store/distributor.go new file mode 100644 index 000000000..f992d4ac4 --- /dev/null +++ b/store/distributor.go @@ -0,0 +1,84 @@ +package store + +import ( + "errors" + + "challenge2016/models" +) + +type distributors struct { + list map[string]models.Distributor +} + +// NewDistributors returns a new instance of Distributors. +func NewDistributors(all models.Geography) Distributors { + return &distributors{ + list: make(map[string]models.Distributor), + } +} + +// Insert inserts a new distributor. +func (d *distributors) Insert(distributor models.Distributor, parentName string) error { + if _, exists := d.list[distributor.Name]; exists { + return errors.New("distributor already exists") + } + + if parentName == "" { + d.list[distributor.Name] = distributor + return nil + } + + parent, exists := d.list[parentName] + if !exists { + return errors.New("parent distributor does not exist") + } + + allowed := false + + for _, region := range distributor.IncludeRegions { + if parent.ValidateIncludedRegions(region) { + allowed = true + break + } + } + + if !allowed { + return errors.New("parent distributor does not allow this distributor") + } + + for _, region := range distributor.ExcludedRegions { + if parent.ValidateExcludedRegions(region) { + allowed = false + break + } + } + + if !allowed { + return errors.New("parent distributor does not allow this distributor") + } + + d.list[distributor.Name] = distributor + + return nil +} + +// Exists returns true if the distributor exists. +func (d *distributors) Exists(name string) bool { + _, exists := d.list[name] + + return exists +} + +// CheckAuthorization returns true if the region is authorized for the distributor. +func (d *distributors) CheckAuthorization(distributor string, region models.Region) (bool, error) { + parent, exists := d.list[distributor] + if !exists { + return false, errors.New("parent distributor does not exist") + } + + if !parent.ValidateRegion(region) { + return false, nil + } + + return true, nil +} From ee55ea5520170dcadc19accbb785259b1f8d1ce8 Mon Sep 17 00:00:00 2001 From: Sreeram Narayanan Date: Tue, 29 Oct 2024 15:03:23 +0530 Subject: [PATCH 14/26] add: Validate in Geography --- models/geography.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/models/geography.go b/models/geography.go index 49f0c2288..59fc0ee6a 100644 --- a/models/geography.go +++ b/models/geography.go @@ -17,3 +17,35 @@ type Province struct { } type Cities map[string]string + +// Validate checks whether the region is valid. +func (g Geography) Validate(r Region) bool { + if r.CountryCode == "" { + return false + } + + _, exists := g[r.CountryCode] + if !exists { + return false + } + + if r.ProvinceCode == "" { + return true + } + + _, exists = g[r.CountryCode].Provinces[r.ProvinceCode] + if !exists { + return false + } + + if r.CityCode == "" { + return true + } + + _, exists = g[r.CountryCode].Provinces[r.ProvinceCode].Cities[r.CityCode] + if !exists { + return false + } + + return true +} From 0ebdaf67d42828456fc89a8c5da31c472c8c1f0b Mon Sep 17 00:00:00 2001 From: Sreeram Narayanan Date: Tue, 29 Oct 2024 15:05:31 +0530 Subject: [PATCH 15/26] update: validate whether Region exists in Geography --- store/distributor.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/store/distributor.go b/store/distributor.go index f992d4ac4..c734b4cb6 100644 --- a/store/distributor.go +++ b/store/distributor.go @@ -7,13 +7,15 @@ import ( ) type distributors struct { - list map[string]models.Distributor + list map[string]models.Distributor + geography models.Geography } // NewDistributors returns a new instance of Distributors. func NewDistributors(all models.Geography) Distributors { return &distributors{ - list: make(map[string]models.Distributor), + list: make(map[string]models.Distributor), + geography: all, } } @@ -36,6 +38,10 @@ func (d *distributors) Insert(distributor models.Distributor, parentName string) allowed := false for _, region := range distributor.IncludeRegions { + if !d.geography.Validate(region) { + return errors.New("invalid region") + } + if parent.ValidateIncludedRegions(region) { allowed = true break @@ -47,6 +53,10 @@ func (d *distributors) Insert(distributor models.Distributor, parentName string) } for _, region := range distributor.ExcludedRegions { + if !d.geography.Validate(region) { + return errors.New("invalid region") + } + if parent.ValidateExcludedRegions(region) { allowed = false break From 372cd2d82702b084bc83741e43beed69a74cacfa Mon Sep 17 00:00:00 2001 From: Sreeram Narayanan Date: Tue, 29 Oct 2024 15:12:49 +0530 Subject: [PATCH 16/26] fix: set parent in Distributors store Insert --- store/distributor.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/store/distributor.go b/store/distributor.go index c734b4cb6..47fe886f4 100644 --- a/store/distributor.go +++ b/store/distributor.go @@ -67,6 +67,8 @@ func (d *distributors) Insert(distributor models.Distributor, parentName string) return errors.New("parent distributor does not allow this distributor") } + distributor.Parent = &parent + d.list[distributor.Name] = distributor return nil From ab2e32f12352c1d90e9003c10e8dc6d7c4c4ddaf Mon Sep 17 00:00:00 2001 From: Sreeram Narayanan Date: Tue, 29 Oct 2024 16:05:38 +0530 Subject: [PATCH 17/26] add: Get by name in Distributors store --- store/distributor.go | 10 ++++++++++ store/store.go | 1 + 2 files changed, 11 insertions(+) diff --git a/store/distributor.go b/store/distributor.go index 47fe886f4..9adef1c8b 100644 --- a/store/distributor.go +++ b/store/distributor.go @@ -94,3 +94,13 @@ func (d *distributors) CheckAuthorization(distributor string, region models.Regi return true, nil } + +// Get returns the distributor. +func (d *distributors) Get(name string) (models.Distributor, error) { + distributor, exists := d.list[name] + if !exists { + return models.Distributor{}, errors.New("distributor does not exist") + } + + return distributor, nil +} diff --git a/store/store.go b/store/store.go index 10a85abd6..e5fdc2683 100644 --- a/store/store.go +++ b/store/store.go @@ -5,5 +5,6 @@ import "challenge2016/models" type Distributors interface { Insert(distributor models.Distributor, parentName string) error Exists(name string) bool + Get(name string) (models.Distributor, error) CheckAuthorization(distributor string, region models.Region) (bool, error) } From 881d9c048dc5c456bfa299b250f52d38e86d10fb Mon Sep 17 00:00:00 2001 From: Sreeram Narayanan Date: Tue, 29 Oct 2024 16:09:16 +0530 Subject: [PATCH 18/26] update: add parent in Distributor Stringer --- models/distributor.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/models/distributor.go b/models/distributor.go index 67f8ba061..dced70a00 100644 --- a/models/distributor.go +++ b/models/distributor.go @@ -18,9 +18,15 @@ func NewDistributor(name string, includeRegions []Region, excludedRegions []Regi // String returns a string representation of the distributor. func (d Distributor) String() string { - res := "Name: " + d.Name + "\n" + res := "Name: " + d.Name - res += "INCLUDE:" + parent := d.Parent + for parent != nil { + res += " > " + parent.Name + parent = parent.Parent + } + + res += "\nINCLUDE:" for _, region := range d.IncludeRegions { res += region.String() + "\n" } From 8582d1f592c192a4292d5f85683db0d755a94337 Mon Sep 17 00:00:00 2001 From: Sreeram Narayanan Date: Tue, 29 Oct 2024 18:33:11 +0530 Subject: [PATCH 19/26] update: return Distributor pointer --- models/distributor.go | 10 ++++++++-- store/distributor.go | 10 +++++++--- store/store.go | 2 +- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/models/distributor.go b/models/distributor.go index dced70a00..54bcedfb2 100644 --- a/models/distributor.go +++ b/models/distributor.go @@ -26,12 +26,18 @@ func (d Distributor) String() string { parent = parent.Parent } - res += "\nINCLUDE:" + if len(d.IncludeRegions) == 0 { + res += "\nINCLUDE:" + } + for _, region := range d.IncludeRegions { res += region.String() + "\n" } - res += "EXCLUDE: " + if len(d.ExcludedRegions) == 0 { + res += "EXCLUDE: " + } + for _, region := range d.ExcludedRegions { res += region.String() + "\n" } diff --git a/store/distributor.go b/store/distributor.go index 9adef1c8b..0c3c663f6 100644 --- a/store/distributor.go +++ b/store/distributor.go @@ -96,11 +96,15 @@ func (d *distributors) CheckAuthorization(distributor string, region models.Regi } // Get returns the distributor. -func (d *distributors) Get(name string) (models.Distributor, error) { +func (d *distributors) Get(name string) (*models.Distributor, error) { + if name == "" { + return nil, errors.New("distributor name is required") + } + distributor, exists := d.list[name] if !exists { - return models.Distributor{}, errors.New("distributor does not exist") + return nil, errors.New("distributor does not exist") } - return distributor, nil + return &distributor, nil } diff --git a/store/store.go b/store/store.go index e5fdc2683..994d2efcf 100644 --- a/store/store.go +++ b/store/store.go @@ -5,6 +5,6 @@ import "challenge2016/models" type Distributors interface { Insert(distributor models.Distributor, parentName string) error Exists(name string) bool - Get(name string) (models.Distributor, error) + Get(name string) (*models.Distributor, error) CheckAuthorization(distributor string, region models.Region) (bool, error) } From e54655c19c80fbc98c9d38c3075b88fd62b2bd69 Mon Sep 17 00:00:00 2001 From: Sreeram Narayanan Date: Tue, 29 Oct 2024 18:33:44 +0530 Subject: [PATCH 20/26] add: cobra cli for commands --- cmd/cli.go | 116 +++++++++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 6 +++ go.sum | 10 +++++ main.go | 18 +++++++++ 4 files changed, 150 insertions(+) create mode 100644 cmd/cli.go create mode 100644 go.sum diff --git a/cmd/cli.go b/cmd/cli.go new file mode 100644 index 000000000..9234dac52 --- /dev/null +++ b/cmd/cli.go @@ -0,0 +1,116 @@ +package cmd + +import ( + "challenge2016/models" + "challenge2016/store" + + "github.com/spf13/cobra" +) + +type CLI struct { + cmd *cobra.Command + store store.Distributors +} + +func NewCLI(store store.Distributors) *CLI { + rootCmd := &cobra.Command{ + Use: "challenge2016", + Short: "A CLI tool to manage distributors", + } + + return &CLI{ + cmd: rootCmd, + store: store, + } +} + +func (c *CLI) Init() error { + distributorCmd := &cobra.Command{ + Use: "distributors", + Short: "Manage distributors", + } + + var ( + name, parent string + include, exclude []string + ) + + addDistributorCmd := &cobra.Command{ + Use: "add", + Short: "Get a distributor", + Run: func(cmd *cobra.Command, args []string) { + var includes, excludes []models.Region + + for _, v := range include { + includes = append(includes, models.Parse(v)) + } + + for _, v := range exclude { + excludes = append(excludes, models.Parse(v)) + } + + dist := models.NewDistributor(name, includes, excludes) + + err := c.store.Insert(dist, parent) + if err != nil { + println(err.Error()) + + return + } + + distributor, err := c.store.Get(name) + if err != nil { + println(err.Error()) + + return + } + + if distributor == nil { + println("Distributor does not exist") + return + } + + println(distributor.String()) + }, + } + + addDistributorCmd.Flags().StringVarP(&name, "name", "n", "", "Distributor name") + addDistributorCmd.Flags().StringSliceVarP(&include, "include", "i", []string{}, "Include regions") + addDistributorCmd.Flags().StringSliceVarP(&exclude, "exclude", "e", []string{}, "Exclude regions") + addDistributorCmd.Flags().StringVarP(&parent, "parent", "p", "", "Parent distributor name") + + err := addDistributorCmd.MarkFlagRequired("name") + if err != nil { + return err + } + + getDistributorCmd := &cobra.Command{ + Use: "get", + Short: "Get a distributor", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + distributor, err := c.store.Get(args[0]) + if err != nil { + println(err.Error()) + + return + } + + println(distributor.String()) + }, + } + + c.cmd.AddCommand(distributorCmd) + distributorCmd.AddCommand(addDistributorCmd, getDistributorCmd) + + return nil +} + +func (c *CLI) Run() error { + err := c.cmd.Execute() + if err != nil { + return err + } + + return nil +} diff --git a/go.mod b/go.mod index 8e4ec9e65..89e6747d5 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,9 @@ module challenge2016 go 1.23.1 + +require ( + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/spf13/cobra v1.8.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 000000000..912390a78 --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index 790580777..66493f429 100644 --- a/main.go +++ b/main.go @@ -1,5 +1,23 @@ package main +import ( + "challenge2016/cmd" + "challenge2016/csv" + "challenge2016/store" +) + func main() { + geography, err := csv.Load("./cities.csv") + if err != nil { + panic(err) + } + + distStore := store.NewDistributors(geography) + + cli := cmd.NewCLI(distStore) + if err = cli.Init(); err != nil { + panic(err) + } + cli.Run() } From 99c94e5768938605bcc495b549f5a2b6fa58279e Mon Sep 17 00:00:00 2001 From: Sreeram Narayanan Date: Tue, 29 Oct 2024 19:05:01 +0530 Subject: [PATCH 21/26] remove: cli --- cmd/cli.go | 116 ----------------------------------------------------- 1 file changed, 116 deletions(-) delete mode 100644 cmd/cli.go diff --git a/cmd/cli.go b/cmd/cli.go deleted file mode 100644 index 9234dac52..000000000 --- a/cmd/cli.go +++ /dev/null @@ -1,116 +0,0 @@ -package cmd - -import ( - "challenge2016/models" - "challenge2016/store" - - "github.com/spf13/cobra" -) - -type CLI struct { - cmd *cobra.Command - store store.Distributors -} - -func NewCLI(store store.Distributors) *CLI { - rootCmd := &cobra.Command{ - Use: "challenge2016", - Short: "A CLI tool to manage distributors", - } - - return &CLI{ - cmd: rootCmd, - store: store, - } -} - -func (c *CLI) Init() error { - distributorCmd := &cobra.Command{ - Use: "distributors", - Short: "Manage distributors", - } - - var ( - name, parent string - include, exclude []string - ) - - addDistributorCmd := &cobra.Command{ - Use: "add", - Short: "Get a distributor", - Run: func(cmd *cobra.Command, args []string) { - var includes, excludes []models.Region - - for _, v := range include { - includes = append(includes, models.Parse(v)) - } - - for _, v := range exclude { - excludes = append(excludes, models.Parse(v)) - } - - dist := models.NewDistributor(name, includes, excludes) - - err := c.store.Insert(dist, parent) - if err != nil { - println(err.Error()) - - return - } - - distributor, err := c.store.Get(name) - if err != nil { - println(err.Error()) - - return - } - - if distributor == nil { - println("Distributor does not exist") - return - } - - println(distributor.String()) - }, - } - - addDistributorCmd.Flags().StringVarP(&name, "name", "n", "", "Distributor name") - addDistributorCmd.Flags().StringSliceVarP(&include, "include", "i", []string{}, "Include regions") - addDistributorCmd.Flags().StringSliceVarP(&exclude, "exclude", "e", []string{}, "Exclude regions") - addDistributorCmd.Flags().StringVarP(&parent, "parent", "p", "", "Parent distributor name") - - err := addDistributorCmd.MarkFlagRequired("name") - if err != nil { - return err - } - - getDistributorCmd := &cobra.Command{ - Use: "get", - Short: "Get a distributor", - Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { - distributor, err := c.store.Get(args[0]) - if err != nil { - println(err.Error()) - - return - } - - println(distributor.String()) - }, - } - - c.cmd.AddCommand(distributorCmd) - distributorCmd.AddCommand(addDistributorCmd, getDistributorCmd) - - return nil -} - -func (c *CLI) Run() error { - err := c.cmd.Execute() - if err != nil { - return err - } - - return nil -} From 0dc2782ba9c1010ea2145244bce978cefe81dcd3 Mon Sep 17 00:00:00 2001 From: Sreeram Narayanan Date: Tue, 29 Oct 2024 19:05:32 +0530 Subject: [PATCH 22/26] fix: format of Region String --- models/distributor.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/models/distributor.go b/models/distributor.go index 54bcedfb2..35326271a 100644 --- a/models/distributor.go +++ b/models/distributor.go @@ -26,7 +26,7 @@ func (d Distributor) String() string { parent = parent.Parent } - if len(d.IncludeRegions) == 0 { + if len(d.IncludeRegions) != 0 { res += "\nINCLUDE:" } @@ -34,7 +34,7 @@ func (d Distributor) String() string { res += region.String() + "\n" } - if len(d.ExcludedRegions) == 0 { + if len(d.ExcludedRegions) != 0 { res += "EXCLUDE: " } From 437f591d027779fc01aee6e5b2ef5a0695669d51 Mon Sep 17 00:00:00 2001 From: Sreeram Narayanan Date: Tue, 29 Oct 2024 19:06:07 +0530 Subject: [PATCH 23/26] add: handler for distributor HTTP APIs --- dto/distributor.go | 8 ++++ dto/region.go | 5 +++ go.mod | 31 ++++++++++++-- go.sum | 92 ++++++++++++++++++++++++++++++++++++++---- handler/distributor.go | 90 +++++++++++++++++++++++++++++++++++++++++ main.go | 23 ++++++++--- 6 files changed, 232 insertions(+), 17 deletions(-) create mode 100644 dto/distributor.go create mode 100644 dto/region.go create mode 100644 handler/distributor.go diff --git a/dto/distributor.go b/dto/distributor.go new file mode 100644 index 000000000..340e876ab --- /dev/null +++ b/dto/distributor.go @@ -0,0 +1,8 @@ +package dto + +type Distributor struct { + Name string `json:"name,omitempty"` + Parent string `json:"parent,omitempty"` + IncludeRegions []string `json:"include,omitempty"` + ExcludedRegions []string `json:"exclude,omitempty"` +} diff --git a/dto/region.go b/dto/region.go new file mode 100644 index 000000000..d88ab0814 --- /dev/null +++ b/dto/region.go @@ -0,0 +1,5 @@ +package dto + +type Region struct { + Region string `json:"region,omitempty"` +} diff --git a/go.mod b/go.mod index 89e6747d5..12d09c1fd 100644 --- a/go.mod +++ b/go.mod @@ -2,8 +2,33 @@ module challenge2016 go 1.23.1 +require github.com/gin-gonic/gin v1.10.0 + require ( - github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/spf13/cobra v1.8.1 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/bytedance/sonic v1.12.3 // indirect + github.com/bytedance/sonic/loader v0.2.1 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.6 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.22.1 // indirect + github.com/goccy/go-json v0.10.3 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.8 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + golang.org/x/arch v0.11.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.30.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect + google.golang.org/protobuf v1.35.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 912390a78..fd90bcf71 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,86 @@ -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/bytedance/sonic v1.12.3 h1:W2MGa7RCU1QTeYRTPE3+88mVC0yXmsRQRChiyVocVjU= +github.com/bytedance/sonic v1.12.3/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E= +github.com/bytedance/sonic/loader v0.2.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +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/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc= +github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA= +github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= +github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= +github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +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/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +golang.org/x/arch v0.11.0 h1:KXV8WWKCXm6tRpLirl2szsO5j/oOODwZf4hATmGVNs4= +golang.org/x/arch v0.11.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= diff --git a/handler/distributor.go b/handler/distributor.go new file mode 100644 index 000000000..cec3823ff --- /dev/null +++ b/handler/distributor.go @@ -0,0 +1,90 @@ +package handler + +import ( + "challenge2016/dto" + "challenge2016/models" + "challenge2016/store" + + "github.com/gin-gonic/gin" +) + +type Distributor struct { + store store.Distributors +} + +// NewDistributor returns a new instance of Distributor. +func NewDistributor(store store.Distributors) *Distributor { + return &Distributor{ + store: store, + } +} + +// Get returns the distributor. +func (d *Distributor) Get(c *gin.Context) { + name := c.Param("name") + dist, err := d.store.Get(name) + if err != nil { + c.JSON(404, gin.H{"error": err.Error()}) + return + } + + c.JSON(200, dist) +} + +// Insert inserts a new distributor. +func (d *Distributor) Insert(c *gin.Context) { + var distributor dto.Distributor + + err := c.BindJSON(&distributor) + if err != nil { + c.JSON(400, gin.H{"error": err.Error()}) + } + + var includes, excludes []models.Region + + for _, region := range distributor.IncludeRegions { + includes = append(includes, models.Parse(region)) + } + + for _, region := range distributor.ExcludedRegions { + excludes = append(excludes, models.Parse(region)) + } + + model := models.Distributor{ + Name: distributor.Name, + IncludeRegions: includes, + ExcludedRegions: excludes, + } + + err = d.store.Insert(model, distributor.Parent) + if err != nil { + c.JSON(400, gin.H{"error": err.Error()}) + return + } + + created, err := d.store.Get(distributor.Name) + if err != nil { + c.JSON(400, gin.H{"error": err.Error()}) + return + } + + c.JSON(201, created) +} + +// Authorization checks whether a distributor is allowed to access a region. +func (d *Distributor) Authorization(c *gin.Context) { + distributor := c.Param("name") + + var region dto.Region + err := c.BindJSON(®ion) + if err != nil { + c.JSON(400, gin.H{"error": err.Error()}) + } + + allowed, err := d.store.CheckAuthorization(distributor, models.Parse(region.Region)) + if err != nil { + c.JSON(400, gin.H{"error": err.Error()}) + } + + c.JSON(200, gin.H{"allowed": allowed}) +} diff --git a/main.go b/main.go index 66493f429..63bcb7de2 100644 --- a/main.go +++ b/main.go @@ -1,9 +1,13 @@ package main import ( - "challenge2016/cmd" + "log/slog" + "challenge2016/csv" + "challenge2016/handler" "challenge2016/store" + + "github.com/gin-gonic/gin" ) func main() { @@ -14,10 +18,17 @@ func main() { distStore := store.NewDistributors(geography) - cli := cmd.NewCLI(distStore) - if err = cli.Init(); err != nil { - panic(err) - } + distributorHandler := handler.NewDistributor(distStore) - cli.Run() + router := gin.Default() + + router.GET("/distributors/:name", distributorHandler.Get) + router.POST("/distributors", distributorHandler.Insert) + router.POST("/distributors/:name/authorization", distributorHandler.Authorization) + + err = router.Run(":8080") + if err != nil { + slog.Error(err.Error()) + return + } } From 1573411f45a3be5c594385cfe36fdcd3bdaae8f5 Mon Sep 17 00:00:00 2001 From: Sreeram Narayanan Date: Tue, 29 Oct 2024 19:23:02 +0530 Subject: [PATCH 24/26] add: ModelToDTO for Distributor --- dto/distributor.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/dto/distributor.go b/dto/distributor.go index 340e876ab..e2e1ecba2 100644 --- a/dto/distributor.go +++ b/dto/distributor.go @@ -1,8 +1,36 @@ package dto +import "challenge2016/models" + type Distributor struct { Name string `json:"name,omitempty"` Parent string `json:"parent,omitempty"` IncludeRegions []string `json:"include,omitempty"` ExcludedRegions []string `json:"exclude,omitempty"` } + +func ModelToDTO(d models.Distributor) Distributor { + var ( + includesStr, excludesStr []string + ) + + for _, region := range d.IncludeRegions { + includesStr = append(includesStr, region.String()) + } + + for _, region := range d.ExcludedRegions { + excludesStr = append(excludesStr, region.String()) + } + + resp := Distributor{ + Name: d.Name, + IncludeRegions: includesStr, + ExcludedRegions: excludesStr, + } + + if d.Parent != nil { + resp.Parent = d.Parent.Name + } + + return resp +} From 16d75f3c827883fe415c8edbc47b247e4b3847d3 Mon Sep 17 00:00:00 2001 From: Sreeram Narayanan Date: Tue, 29 Oct 2024 19:23:24 +0530 Subject: [PATCH 25/26] fix: return on error in handler --- handler/distributor.go | 33 ++++++++++++++++++++++++--------- store/distributor.go | 5 +++-- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/handler/distributor.go b/handler/distributor.go index cec3823ff..6deff99dc 100644 --- a/handler/distributor.go +++ b/handler/distributor.go @@ -1,6 +1,8 @@ package handler import ( + "net/http" + "challenge2016/dto" "challenge2016/models" "challenge2016/store" @@ -24,11 +26,16 @@ func (d *Distributor) Get(c *gin.Context) { name := c.Param("name") dist, err := d.store.Get(name) if err != nil { - c.JSON(404, gin.H{"error": err.Error()}) + c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) return } - c.JSON(200, dist) + if dist == nil { + c.JSON(http.StatusNotFound, gin.H{"error": "distributor not found"}) + return + } + + c.JSON(http.StatusOK, dto.ModelToDTO(*dist)) } // Insert inserts a new distributor. @@ -37,7 +44,7 @@ func (d *Distributor) Insert(c *gin.Context) { err := c.BindJSON(&distributor) if err != nil { - c.JSON(400, gin.H{"error": err.Error()}) + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) } var includes, excludes []models.Region @@ -58,17 +65,22 @@ func (d *Distributor) Insert(c *gin.Context) { err = d.store.Insert(model, distributor.Parent) if err != nil { - c.JSON(400, gin.H{"error": err.Error()}) + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } created, err := d.store.Get(distributor.Name) if err != nil { - c.JSON(400, gin.H{"error": err.Error()}) + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if created == nil { + c.JSON(http.StatusNotFound, gin.H{"error": "distributor not found"}) return } - c.JSON(201, created) + c.JSON(http.StatusCreated, dto.ModelToDTO(*created)) } // Authorization checks whether a distributor is allowed to access a region. @@ -76,15 +88,18 @@ func (d *Distributor) Authorization(c *gin.Context) { distributor := c.Param("name") var region dto.Region + err := c.BindJSON(®ion) if err != nil { - c.JSON(400, gin.H{"error": err.Error()}) + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return } allowed, err := d.store.CheckAuthorization(distributor, models.Parse(region.Region)) if err != nil { - c.JSON(400, gin.H{"error": err.Error()}) + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return } - c.JSON(200, gin.H{"allowed": allowed}) + c.JSON(http.StatusOK, gin.H{"allowed": allowed}) } diff --git a/store/distributor.go b/store/distributor.go index 0c3c663f6..55fa7ad23 100644 --- a/store/distributor.go +++ b/store/distributor.go @@ -21,7 +21,8 @@ func NewDistributors(all models.Geography) Distributors { // Insert inserts a new distributor. func (d *distributors) Insert(distributor models.Distributor, parentName string) error { - if _, exists := d.list[distributor.Name]; exists { + _, exists := d.list[distributor.Name] + if exists { return errors.New("distributor already exists") } @@ -85,7 +86,7 @@ func (d *distributors) Exists(name string) bool { func (d *distributors) CheckAuthorization(distributor string, region models.Region) (bool, error) { parent, exists := d.list[distributor] if !exists { - return false, errors.New("parent distributor does not exist") + return false, errors.New("distributor does not exist") } if !parent.ValidateRegion(region) { From 5ae909fde76ca309732f46f6a6857439c1150388 Mon Sep 17 00:00:00 2001 From: Sreeram Narayanan Date: Tue, 29 Oct 2024 20:21:44 +0530 Subject: [PATCH 26/26] add: OpenAPI spec --- openapi.yaml | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 openapi.yaml diff --git a/openapi.yaml b/openapi.yaml new file mode 100644 index 000000000..b9cb8c958 --- /dev/null +++ b/openapi.yaml @@ -0,0 +1,96 @@ +openapi: 3.1.0 +info: + title: Title + description: Title + version: 1.0.0 +servers: + - url: 'http://localhost:8080' +paths: + /distributors/{name}: + get: + summary: Get a distributor by name. + description: Description + operationId: OperationId + parameters: + - name: name + in: query + required: true + schema: + type: string + description: Description + responses: + '200': + description: Description + content: + application/json: + schema: + $ref: '#/components/schemas/Distributor' + /distributors: + post: + summary: Create a new distributor. + description: Description + operationId: OperationId + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Distributor' + responses: + '200': + description: Description + content: + application/json: + schema: + $ref: '#/components/schemas/Distributor' + /distributors/{name}/authorization: + post: + summary: Check if a distributor is allowed to access a region. + description: Description + operationId: OperationId + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Region' + responses: + '200': + description: Description + content: + application/json: + schema: + $ref: '#/components/schemas/Authorization' +components: + schemas: + Distributor: + type: object + properties: + name: + type: string + parent: + type: string + include: + type: array + items: + type: string + exclude: + type: array + items: + type: string + required: + - name + Authorization: + type: object + properties: + allowed: + type: boolean + required: + - allowed + Region: + type: object + properties: + region: + type: string + required: + - region