diff --git a/go.mod b/go.mod new file mode 100644 index 000000000..12f46939b --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module qc_assignment + +go 1.22.2 diff --git a/main.go b/main.go new file mode 100644 index 000000000..61c2e6150 --- /dev/null +++ b/main.go @@ -0,0 +1,100 @@ +package main + +import ( + "bufio" + "fmt" + "log" + "os" + "qc_assignment/models" + "qc_assignment/services" + "strconv" + "strings" +) + +func main() { + err := services.LoadCSVIntoStruct("cities.csv") + if err != nil { + log.Println("error loading csv file") + } + + for { + reader := bufio.NewReader(os.Stdin) + fmt.Println("Enter the operation you want to perform:") + fmt.Println("1 - Create new Distributor") + fmt.Println("2 - Create Sub-Distributor") + var optionStr string + optionStr, _ = reader.ReadString('\n') + optionStr = strings.TrimSpace(optionStr) + option, _ := strconv.Atoi(optionStr) + + if option == 1 { + fmt.Println("Enter the name of the distributor: ") + name, _ := reader.ReadString('\n') + name = strings.TrimSpace(name) + if services.IsExistsDistributor(name) { + fmt.Println("Error: Distributor already exists. Please try again.") + continue + } + fmt.Println("Enter the permissions in the form -- \nOR - \nOR .\nEnter 'DONE' to stop:") + var permissions []models.Permission + for { + line, _ := reader.ReadString('\n') + line = strings.TrimSpace(line) + if line == "DONE" { + break + } + lineSplit := strings.Split(line, " ") + permissions = append(permissions, models.Permission{ + Action: lineSplit[0], + Area: lineSplit[1], + }) + } + distributor := models.CreateNewEmptyDistributor() + services.CreateDistributor(name, distributor) + distributor.AddPermissions(permissions) + fmt.Printf("Distributor %s created.\n", name) + } else if option == 2 { + // Check if the primary distributor exists + fmt.Println("Enter the name of the primary distributor: ") + primaryDistributorName, _ := reader.ReadString('\n') + primaryDistributorName = strings.TrimSpace(primaryDistributorName) + if !services.IsExistsDistributor(primaryDistributorName) { + fmt.Println("Error: Primary Distributor does not exist. Please try again.") + continue + } + // Create secondary distributor + fmt.Println("Enter the name of the Secondary Distributor: ") + secondaryDistributorName, _ := reader.ReadString('\n') + secondaryDistributorName = strings.TrimSpace(secondaryDistributorName) + if services.IsExistsDistributor(secondaryDistributorName) { + fmt.Println("Error: Distributor already exists. Please try again.") + continue + } + fmt.Println("Enter the permissions in the form -- \nOR - \nOR .\nEnter 'DONE' to stop:") + var permissions []models.Permission + for { + line, _ := reader.ReadString('\n') + line = strings.TrimSpace(line) + if line == "DONE" { + break + } + lineSplit := strings.Split(line, " ") + permissions = append(permissions, models.Permission{ + Action: lineSplit[0], + Area: lineSplit[1], + }) + } + parentDistributor := services.GetDistributor(primaryDistributorName) + check := services.CheckDistributor(parentDistributor, permissions) + if !check { + fmt.Println("Error: Parent distributor can't create distributor with these permissions. Please try again.") + continue + } + secondaryDistributor := models.CreateNewEmptyDistributor() + secondaryDistributor.AddPermissions(permissions) + fmt.Printf("Distributor %s created.\n", secondaryDistributorName) + } else { + fmt.Println("Invalid option entered. Please try again.") + } + } +} diff --git a/models/constants.go b/models/constants.go new file mode 100644 index 000000000..3f204f3c1 --- /dev/null +++ b/models/constants.go @@ -0,0 +1,4 @@ +package models + +const INCLUDE = "INCLUDE" +const EXCLUDE = "EXCLUDE" diff --git a/models/distributor.go b/models/distributor.go new file mode 100644 index 000000000..dce433a80 --- /dev/null +++ b/models/distributor.go @@ -0,0 +1,72 @@ +package models + +import ( + "errors" + "strings" +) + +type Distributor struct { + Name string + ParentName *Distributor + Included map[string]struct{} + Excluded map[string]struct{} +} + +func CreateNewEmptyDistributor() *Distributor { + return &Distributor{ + Name: "", + ParentName: &Distributor{}, + Included: map[string]struct{}{}, + Excluded: map[string]struct{}{}, + } +} + +type Permission struct { + Action string + Area string +} + +// The area string will be of the form country, state-country, or city-state-country + +func (d *Distributor) AddPermissions(permissions []Permission) error { + for _, permission := range permissions { + action := permission.Action + area := permission.Area + areas := strings.Split(area, "-") + if action == INCLUDE { + for _, i := range areas { + d.Included[i] = struct{}{} + } + } else if action == EXCLUDE { + for _, i := range areas { + d.Excluded[i] = struct{}{} + delete(d.Included, i) + } + } else { + return errors.New("invalid action") + } + } + return nil +} + +func (d *Distributor) IsIncluded(area string) bool { + areas := strings.Split(area, "-") + for _, i := range areas { + _, exists := d.Included[i] + if exists { + return true + } + } + return false +} + +func (d *Distributor) IsExcluded(area string) bool { + areas := strings.Split(area, "-") + for _, i := range areas { + _, exists := d.Excluded[i] + if !exists { + return false + } + } + return true +} diff --git a/models/node.go b/models/node.go new file mode 100644 index 000000000..53c32bd62 --- /dev/null +++ b/models/node.go @@ -0,0 +1,19 @@ +package models + +type Node struct { + Name string + Child map[string]*Node +} + +func UpsertNode(parent *Node, name string) *Node { + childNode, exists := parent.Child[name] + if exists { + return childNode + } + childNode = &Node{ + Name: name, + Child: map[string]*Node{}, + } + parent.Child[name] = childNode + return childNode +} diff --git a/services/distributor.service.go b/services/distributor.service.go new file mode 100644 index 000000000..bcda1943e --- /dev/null +++ b/services/distributor.service.go @@ -0,0 +1,45 @@ +package services + +import "qc_assignment/models" + +var distributors map[string]*models.Distributor + +func init() { // Initialize map to store all the distributors + distributors = make(map[string]*models.Distributor) +} + +func CreateDistributor(name string, d *models.Distributor) { + distributors[name] = d +} + +func CheckDistributor(parent *models.Distributor, permissions []models.Permission) bool { // Check if a sub-distributor can be created with the parent distributor. + for _, permission := range permissions { + action := permission.Action + area := permission.Area + + if action == models.INCLUDE { + if !parent.IsIncluded(area) { + return false + } + } + if action == models.EXCLUDE { + if parent.IsExcluded(area) { + return false + } + } + } + return true +} + +func IsExistsDistributor(name string) bool { + _, exists := distributors[name] + return exists +} + +func GetDistributor(name string) *models.Distributor { + d, exists := distributors[name] + if exists { + return d + } + return nil +} diff --git a/services/file_loader.service.go b/services/file_loader.service.go new file mode 100644 index 000000000..d9a3141b8 --- /dev/null +++ b/services/file_loader.service.go @@ -0,0 +1,33 @@ +package services + +import ( + "bufio" + "os" + "qc_assignment/models" + "strings" +) + +func LoadCSVIntoStruct(path string) error { + f, err := os.Open(path) + if err != nil { + return nil + } + defer f.Close() + + scanner := bufio.NewScanner(f) + // Skipping the first line + scanner.Scan() + root := &models.Node{ + Name: "countries", + Child: map[string]*models.Node{}, + } + for scanner.Scan() { + line := scanner.Text() + splitLine := strings.Split(line, ",") + country, state, city := splitLine[3], splitLine[4], splitLine[5] + countryNode := models.UpsertNode(root, country) + stateNode := models.UpsertNode(countryNode, state) + models.UpsertNode(stateNode, city) + } + return nil +}