From 022a5797f1b59d4ac7d2b0d46218b75d04aa0c09 Mon Sep 17 00:00:00 2001 From: ansh3196 Date: Fri, 21 Feb 2025 19:10:53 +0530 Subject: [PATCH 1/4] Intial commit --- go.mod | 3 + main.go | 300 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 303 insertions(+) create mode 100644 go.mod create mode 100644 main.go diff --git a/go.mod b/go.mod new file mode 100644 index 000000000..28033c483 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module movie-distrbution + +go 1.21.6 diff --git a/main.go b/main.go new file mode 100644 index 000000000..f40f36e8d --- /dev/null +++ b/main.go @@ -0,0 +1,300 @@ +package main + +import ( + "encoding/csv" + "flag" + "fmt" + "io" + "os" + "strings" +) + +// Location represents a geographical location with both codes and names +type Location struct { + CityCode string + ProvinceCode string + CountryCode string + CityName string + ProvinceName string + CountryName string +} + +// Distributor represents a distribution entity with its permissions +type Distributor struct { + Name string + Parent *Distributor + Includes map[string]bool + Excludes map[string]bool + Locations map[string]*Location // Maps location codes to full location info +} + +func NewDistributor(name string, parent *Distributor) *Distributor { + return &Distributor{ + Name: name, + Parent: parent, + Includes: make(map[string]bool), + Excludes: make(map[string]bool), + Locations: make(map[string]*Location), + } +} + +// DistributionSystem manages all distributors +type DistributionSystem struct { + distributors map[string]*Distributor + locations map[string]*Location +} + +// NewDistributionSystem creates a new system instance +func NewDistributionSystem() *DistributionSystem { + return &DistributionSystem{ + distributors: make(map[string]*Distributor), + locations: make(map[string]*Location), + } +} + +// LoadLocationData loads geographical data from CSV +func (ds *DistributionSystem) LoadLocationData(filename string) error { + file, err := os.Open(filename) + if err != nil { + return err + } + defer file.Close() + + reader := csv.NewReader(file) + // Skip header + _, err = reader.Read() + if err != nil { + return err + } + + for { + record, err := reader.Read() + if err == io.EOF { + break + } + if err != nil { + return err + } + + if len(record) >= 6 { + location := &Location{ + CityCode: record[0], + ProvinceCode: record[1], + CountryCode: record[2], + CityName: record[3], + ProvinceName: record[4], + CountryName: record[5], + } + + cityKey := fmt.Sprintf("%s-%s-%s", location.CityCode, location.ProvinceCode, location.CountryCode) + provinceKey := fmt.Sprintf("%s-%s", location.ProvinceCode, location.CountryCode) + countryKey := location.CountryCode + + ds.locations[cityKey] = location + ds.locations[provinceKey] = location + ds.locations[countryKey] = location + } + } + return nil +} + +func (d *Distributor) AddPermission(permission string, isInclude bool) error { + if d.Parent != nil { + // Verify permission is valid with respect to parent + if !d.Parent.HasPermission(permission) { + return fmt.Errorf("parent distributor does not have permission for: %s", permission) + } + } + + if isInclude { + d.Includes[permission] = true + } else { + d.Excludes[permission] = true + } + return nil +} + +// HasPermission checks if distribution is allowed in the given region +func (d *Distributor) HasPermission(region string) bool { + parts := strings.Split(region, "-") + + // Check excludes first + for excluded := range d.Excludes { + excludedParts := strings.Split(excluded, "-") + if isSubregion(parts, excludedParts) { + return false + } + } + + // Check includes + for included := range d.Includes { + includedParts := strings.Split(included, "-") + if isSubregion(parts, includedParts) { + // Check parent permissions if exists + if d.Parent != nil { + return d.Parent.HasPermission(region) + } + return true + } + } + + return false +} + +func isSubregion(region1, region2 []string) bool { + // If region2 is a country code + if len(region2) == 1 { + return region1[len(region1)-1] == region2[0] + } + + // If region2 is a province-country code + if len(region2) == 2 { + return len(region1) >= 2 && + region1[len(region1)-2] == region2[0] && + region1[len(region1)-1] == region2[1] + } + + // If region2 is a city-province-country code + if len(region2) == 3 { + return len(region1) == 3 && + region1[0] == region2[0] && + region1[1] == region2[1] && + region1[2] == region2[2] + } + + return false +} + +// ValidateRegion checks if a region code exists in the loaded data +func (d *Distributor) ValidateRegion(region string) bool { + _, exists := d.Locations[region] + return exists +} + +// AddDistributor adds a new distributor to the system +func (ds *DistributionSystem) AddDistributor(name string, parentName string) error { + if _, exists := ds.distributors[name]; exists { + return fmt.Errorf("distributor %s already exists", name) + } + + var parent *Distributor + if parentName != "" { + var exists bool + parent, exists = ds.distributors[parentName] + if !exists { + return fmt.Errorf("parent distributor %s does not exist", parentName) + } + } + + distributor := NewDistributor(name, parent) + distributor.Locations = ds.locations + ds.distributors[name] = distributor + return nil +} + +// AddPermission adds a permission for a distributor +func (ds *DistributionSystem) AddPermission(distributorName, region string, isInclude bool) error { + distributor, exists := ds.distributors[distributorName] + if !exists { + return fmt.Errorf("distributor %s does not exist", distributorName) + } + + if !ds.ValidateRegion(region) { + return fmt.Errorf("invalid region code: %s", region) + } + + return distributor.AddPermission(region, isInclude) +} + +// CheckPermission checks if a distributor has permission for a region +func (ds *DistributionSystem) CheckPermission(distributorName, region string) (bool, error) { + distributor, exists := ds.distributors[distributorName] + if !exists { + return false, fmt.Errorf("distributor %s does not exist", distributorName) + } + + if !ds.ValidateRegion(region) { + return false, fmt.Errorf("invalid region code: %s", region) + } + + return distributor.HasPermission(region), nil +} + +// ValidateRegion checks if a region code exists +func (ds *DistributionSystem) ValidateRegion(region string) bool { + _, exists := ds.locations[region] + return exists +} + +func main() { + // Command line flags + csvFile := flag.String("csv", "cities.csv", "Path to the locations CSV file") + command := flag.String("cmd", "", "Command to execute (add-distributor, add-permission, check)") + distributorName := flag.String("distributor", "", "Distributor name") + parentName := flag.String("parent", "", "Parent distributor name (for add-distributor)") + region := flag.String("region", "", "Region code") + permissionType := flag.String("type", "include", "Permission type (include/exclude)") + + flag.Parse() + + // Initialize system + system := NewDistributionSystem() + err := system.LoadLocationData(*csvFile) + if err != nil { + fmt.Printf("Error loading location data: %v\n", err) + return + } + + switch *command { + case "add-distributor": + if *distributorName == "" { + fmt.Println("Error: distributor name is required") + return + } + err := system.AddDistributor(*distributorName, *parentName) + if err != nil { + fmt.Printf("Error adding distributor: %v\n", err) + return + } + fmt.Printf("Successfully added distributor: %s\n", *distributorName) + + case "add-permission": + if *distributorName == "" || *region == "" { + fmt.Println("Error: distributor name and region are required") + return + } + isInclude := *permissionType == "include" + err := system.AddPermission(*distributorName, *region, isInclude) + if err != nil { + fmt.Printf("Error adding permission: %v\n", err) + return + } + fmt.Printf("Successfully added %s permission for %s to %s\n", + *permissionType, *region, *distributorName) + + case "check": + if *distributorName == "" || *region == "" { + fmt.Println("Error: distributor name and region are required") + return + } + hasPermission, err := system.CheckPermission(*distributorName, *region) + if err != nil { + fmt.Printf("Error checking permission: %v\n", err) + return + } + location := system.locations[*region] + fmt.Printf("Permission check for %s:\n", *distributorName) + fmt.Printf("Region: %s (%s, %s, %s)\n", + *region, location.CityName, location.ProvinceName, location.CountryName) + fmt.Printf("Result: %v\n", hasPermission) + + default: + fmt.Println("Usage:") + fmt.Println("1. Add distributor:") + fmt.Println(" go run main.go -cmd=add-distributor -distributor=DIST1 [-parent=PARENTDIST]") + fmt.Println("\n2. Add permission:") + fmt.Println(" go run main.go -cmd=add-permission -distributor=DIST1 -region=REGION-CODE(City Code-Province Code-Country Code) -type=include/exclude") + fmt.Println("\n3. Check permission:") + fmt.Println(" go run main.go -cmd=check -distributor=DIST1 -region=REGION-CODE(City Code-Province Code-Country Code)") + } +} From b364a6910e560ac1e1952d398793013ac0a28141 Mon Sep 17 00:00:00 2001 From: ansh3196 Date: Sat, 22 Feb 2025 14:57:57 +0530 Subject: [PATCH 2/4] added json file for storing the distributor data --- distributors.json | 10 +++ main.go | 160 ++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 150 insertions(+), 20 deletions(-) create mode 100644 distributors.json diff --git a/distributors.json b/distributors.json new file mode 100644 index 000000000..0623b7adb --- /dev/null +++ b/distributors.json @@ -0,0 +1,10 @@ +{ + "DIST1": { + "Name": "DIST1", + "ParentName": "", + "Includes": { + "HR-IN": true + }, + "Excludes": {} + } +} diff --git a/main.go b/main.go index f40f36e8d..4e9296552 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,7 @@ package main import ( "encoding/csv" + "encoding/json" "flag" "fmt" "io" @@ -19,6 +20,14 @@ type Location struct { CountryName string } +// DistributorData represents the data to be persisted +type DistributorData struct { + Name string + ParentName string + Includes map[string]bool + Excludes map[string]bool +} + // Distributor represents a distribution entity with its permissions type Distributor struct { Name string @@ -98,6 +107,79 @@ func (ds *DistributionSystem) LoadLocationData(filename string) error { return nil } +// LoadState loads distributor data from the JSON file +func (ds *DistributionSystem) LoadState(filename string) error { + file, err := os.OpenFile(filename, os.O_RDONLY|os.O_CREATE, 0644) + if err != nil { + return err + } + defer file.Close() + + stat, err := file.Stat() + if err != nil { + return err + } + + if stat.Size() == 0 { + return nil // Empty file, no data to load + } + + var distributorsData map[string]DistributorData + decoder := json.NewDecoder(file) + if err := decoder.Decode(&distributorsData); err != nil { + return err + } + + // First pass: create all distributors + for name, data := range distributorsData { + dist := NewDistributor(name, nil) + dist.Includes = data.Includes + dist.Excludes = data.Excludes + dist.Locations = ds.locations + ds.distributors[name] = dist + } + + // Second pass: set up parent relationships + for name, data := range distributorsData { + if data.ParentName != "" { + if parent, exists := ds.distributors[data.ParentName]; exists { + ds.distributors[name].Parent = parent + } + } + } + + return nil +} + +// SaveState saves distributor data to the JSON file +func (ds *DistributionSystem) SaveState(filename string) error { + distributorsData := make(map[string]DistributorData) + + for name, dist := range ds.distributors { + var parentName string + if dist.Parent != nil { + parentName = dist.Parent.Name + } + + distributorsData[name] = DistributorData{ + Name: dist.Name, + ParentName: parentName, + Includes: dist.Includes, + Excludes: dist.Excludes, + } + } + + file, err := os.Create(filename) + if err != nil { + return err + } + defer file.Close() + + encoder := json.NewEncoder(file) + encoder.SetIndent("", " ") + return encoder.Encode(distributorsData) +} + func (d *Distributor) AddPermission(permission string, isInclude bool) error { if d.Parent != nil { // Verify permission is valid with respect to parent @@ -165,12 +247,6 @@ func isSubregion(region1, region2 []string) bool { return false } -// ValidateRegion checks if a region code exists in the loaded data -func (d *Distributor) ValidateRegion(region string) bool { - _, exists := d.Locations[region] - return exists -} - // AddDistributor adds a new distributor to the system func (ds *DistributionSystem) AddDistributor(name string, parentName string) error { if _, exists := ds.distributors[name]; exists { @@ -226,10 +302,32 @@ func (ds *DistributionSystem) ValidateRegion(region string) bool { return exists } +// ListDistributors prints all distributors and their permissions +func (ds *DistributionSystem) ListDistributors() { + fmt.Println("Registered Distributors:") + for name, dist := range ds.distributors { + parentName := "none" + if dist.Parent != nil { + parentName = dist.Parent.Name + } + fmt.Printf("- %s (Parent: %s)\n", name, parentName) + fmt.Println(" Includes:") + for region := range dist.Includes { + fmt.Printf(" - %s\n", region) + } + fmt.Println(" Excludes:") + for region := range dist.Excludes { + fmt.Printf(" - %s\n", region) + } + fmt.Println() + } +} + func main() { // Command line flags csvFile := flag.String("csv", "cities.csv", "Path to the locations CSV file") - command := flag.String("cmd", "", "Command to execute (add-distributor, add-permission, check)") + dataFile := flag.String("data", "distributors.json", "Path to the distributors data file") + command := flag.String("cmd", "", "Command to execute (add-distributor, add-permission, check, list)") distributorName := flag.String("distributor", "", "Distributor name") parentName := flag.String("parent", "", "Parent distributor name (for add-distributor)") region := flag.String("region", "", "Region code") @@ -245,18 +343,28 @@ func main() { return } + // Load existing distributor data + err = system.LoadState(*dataFile) + if err != nil { + fmt.Printf("Error loading distributor data: %v\n", err) + return + } + + var cmdErr error switch *command { + case "list": + system.ListDistributors() + return + case "add-distributor": if *distributorName == "" { fmt.Println("Error: distributor name is required") return } - err := system.AddDistributor(*distributorName, *parentName) - if err != nil { - fmt.Printf("Error adding distributor: %v\n", err) - return + cmdErr = system.AddDistributor(*distributorName, *parentName) + if cmdErr == nil { + fmt.Printf("Successfully added distributor: %s\n", *distributorName) } - fmt.Printf("Successfully added distributor: %s\n", *distributorName) case "add-permission": if *distributorName == "" || *region == "" { @@ -264,13 +372,11 @@ func main() { return } isInclude := *permissionType == "include" - err := system.AddPermission(*distributorName, *region, isInclude) - if err != nil { - fmt.Printf("Error adding permission: %v\n", err) - return + cmdErr = system.AddPermission(*distributorName, *region, isInclude) + if cmdErr == nil { + fmt.Printf("Successfully added %s permission for %s to %s\n", + *permissionType, *region, *distributorName) } - fmt.Printf("Successfully added %s permission for %s to %s\n", - *permissionType, *region, *distributorName) case "check": if *distributorName == "" || *region == "" { @@ -293,8 +399,22 @@ func main() { fmt.Println("1. Add distributor:") fmt.Println(" go run main.go -cmd=add-distributor -distributor=DIST1 [-parent=PARENTDIST]") fmt.Println("\n2. Add permission:") - fmt.Println(" go run main.go -cmd=add-permission -distributor=DIST1 -region=REGION-CODE(City Code-Province Code-Country Code) -type=include/exclude") + fmt.Println(" go run main.go -cmd=add-permission -distributor=DIST1 -region=REGION-CODE -type=include/exclude") fmt.Println("\n3. Check permission:") - fmt.Println(" go run main.go -cmd=check -distributor=DIST1 -region=REGION-CODE(City Code-Province Code-Country Code)") + fmt.Println(" go run main.go -cmd=check -distributor=DIST1 -region=REGION-CODE") + fmt.Println("\n4. List all distributors:") + fmt.Println(" go run main.go -cmd=list") + } + + if cmdErr != nil { + fmt.Printf("Error: %v\n", cmdErr) + return + } + + // Save state after successful command execution + if *command != "check" && *command != "list" { + if err := system.SaveState(*dataFile); err != nil { + fmt.Printf("Error saving state: %v\n", err) + } } } From b2a162fd36038e406f204f4d51e15a5d7a8ee255 Mon Sep 17 00:00:00 2001 From: ansh3196 Date: Sat, 22 Feb 2025 15:17:38 +0530 Subject: [PATCH 3/4] new:tested exclucded values --- distributors.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/distributors.json b/distributors.json index 0623b7adb..8084b0ffa 100644 --- a/distributors.json +++ b/distributors.json @@ -3,8 +3,11 @@ "Name": "DIST1", "ParentName": "", "Includes": { - "HR-IN": true + "HR-IN": true, + "IN": true }, - "Excludes": {} + "Excludes": { + "ZHEAB-TG-IN": true + } } } From 5bd88c313e73fcfde3e952e7484766b12c847f3d Mon Sep 17 00:00:00 2001 From: ansh3196 Date: Sat, 22 Feb 2025 15:56:13 +0530 Subject: [PATCH 4/4] Refactor:added comments to funcations --- distributors.json | 6 ++++++ main.go | 6 +++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/distributors.json b/distributors.json index 8084b0ffa..99d9d4207 100644 --- a/distributors.json +++ b/distributors.json @@ -9,5 +9,11 @@ "Excludes": { "ZHEAB-TG-IN": true } + }, + "DIST2": { + "Name": "DIST2", + "ParentName": "DIST1", + "Includes": {}, + "Excludes": {} } } diff --git a/main.go b/main.go index 4e9296552..2820a7384 100644 --- a/main.go +++ b/main.go @@ -121,7 +121,7 @@ func (ds *DistributionSystem) LoadState(filename string) error { } if stat.Size() == 0 { - return nil // Empty file, no data to load + return nil } var distributorsData map[string]DistributorData @@ -335,7 +335,7 @@ func main() { flag.Parse() - // Initialize system + // Initialize data and distributors from csv and json file system := NewDistributionSystem() err := system.LoadLocationData(*csvFile) if err != nil { @@ -411,7 +411,7 @@ func main() { return } - // Save state after successful command execution + // Save state after successful command execution in json file if *command != "check" && *command != "list" { if err := system.SaveState(*dataFile); err != nil { fmt.Printf("Error saving state: %v\n", err)