diff --git a/HowToRun.md b/HowToRun.md new file mode 100644 index 000000000..3e021adf3 --- /dev/null +++ b/HowToRun.md @@ -0,0 +1,27 @@ +Find the Below Example Commands For Each of the Following : + +User Flow + +Choose an option: +1. Add new permission +2. Get Distributor With Name +3. Check Distributor Permission +4. Exit + +Addding single distributor: + +1. slect option one after initial promt (1) +2. Add distributor name and click enter(distributor1) +3. Add permissions as many as you want and if you want to exit enter EXIT(EXIT) +4. then you can check the distributor by selecting option 2 (2) +5. enter the distributor name and it will fetch all the details +6. checking individual region permissions select option (3) +7. Enter Distributor Name +8. enter the permissons with format (NAME : CITY-PROVINCE_COUNTRY)exqample (distributor1) + + +For Adding PArent Child Distributor: + +All the things follow same flow +only while adding names : Child < Parent +this is the only change diff --git a/go.mod b/go.mod new file mode 100644 index 000000000..3f6099e06 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module challange2016 + +go 1.23.3 diff --git a/internal/models/cahceinitialisor.go b/internal/models/cahceinitialisor.go new file mode 100644 index 000000000..532ea651e --- /dev/null +++ b/internal/models/cahceinitialisor.go @@ -0,0 +1,18 @@ +package models + +type DistributionMaps struct { + CityMap map[string]*Location + ProvinceMap map[string]*Location + CountryMap map[string]*Location + Distributor map[string]*Distributor +} + +// factory function +func NewDistributionMaps() *DistributionMaps { + return &DistributionMaps{ + CityMap: make(map[string]*Location), + ProvinceMap: make(map[string]*Location), + CountryMap: make(map[string]*Location), + Distributor: make(map[string]*Distributor), + } +} diff --git a/internal/models/distributor.go b/internal/models/distributor.go new file mode 100644 index 000000000..08e275e9f --- /dev/null +++ b/internal/models/distributor.go @@ -0,0 +1,22 @@ +package models + +type Distributor struct { + Name string + Include []Location + Exclude []Location + ParentDistributor *string +} + +type Location struct { + City string + CityCode string + Province string + ProvinceCode string + Country string + CountryCode string +} + +type CheckPermission struct { + DistributorName *string + Loc *Location +} diff --git a/internal/service/interface.go b/internal/service/interface.go new file mode 100644 index 000000000..272e57e10 --- /dev/null +++ b/internal/service/interface.go @@ -0,0 +1,9 @@ +package service + +import "challange2016/internal/models" + +type Service interface { + AddDistributor(reqBody *models.Distributor) (*models.Distributor, error) + GetDistributorByName(distributorName *string) (*models.Distributor, error) + CheckDistributorPermission(distributorName *string, reqBody models.CheckPermission) bool +} diff --git a/internal/service/service.go b/internal/service/service.go new file mode 100644 index 000000000..a390a5959 --- /dev/null +++ b/internal/service/service.go @@ -0,0 +1,155 @@ +package service + +import ( + "challange2016/internal/models" + "challange2016/internal/store" + "errors" + "strings" +) + +type service struct { + store store.Store +} + +func New(store store.Store) *service { + return &service{ + store: store, + } +} + +func (s *service) AddDistributor(reqBody *models.Distributor) (*models.Distributor, error) { + reqBody.Name = strings.ToUpper(reqBody.Name) + + // check whether distributor already exist or not + distributor := s.store.GetDistributorByName(reqBody.Name) + if distributor != nil { + return nil, errors.New("distributor already exist") + } + // initilise include fields + reqBody.Include = s.AutoInitialiseFields(reqBody.Include) + // initialise exclude fields + reqBody.Exclude = s.AutoInitialiseFields(reqBody.Exclude) + // if parentDistributor exist, assign exclude fields also + if reqBody.ParentDistributor != nil { + isAllowed := s.checkParentDistributorPermissions(reqBody) + if !isAllowed { + return nil, errors.New("Parent Distributor doesn't have permission to distribute these location") + } + upperCaseName := strings.ToUpper(*reqBody.ParentDistributor) + reqBody.ParentDistributor = &upperCaseName + } + // store layer call + response := s.store.AddDistributor(reqBody) + + return response, nil +} + +func (s *service) checkParentDistributorPermissions(distributor *models.Distributor) bool { + parentDistributor := s.store.GetDistributorByName(strings.ToUpper(*distributor.ParentDistributor)) + if parentDistributor == nil { + return false + } + for _, loc := range distributor.Include { + isIncludeAllowed := checkPermission(parentDistributor.Include, loc) + isExcludeAllowed := checkPermission(parentDistributor.Exclude, loc) + + if isExcludeAllowed || !isIncludeAllowed { + return false + } + } + + return true +} + +func (s *service) GetDistributorByName(distributorName *string) (*models.Distributor, error) { + if distributorName == nil || *distributorName == "" { + return nil, errors.New("empty distributor name value") + } + + *distributorName = strings.ToUpper(*distributorName) + + distributor := s.store.GetDistributorByName(*distributorName) + if distributor == nil { + return nil, errors.New("entity not found") + } + + return distributor, nil +} + +func (s *service) CheckDistributorPermission(distributorName *string, reqBody models.CheckPermission) bool { + switch { + case reqBody.DistributorName == nil: + return false + case reqBody.Loc == nil: + return false + case reqBody.Loc.City == "" || reqBody.Loc.Province == "" || reqBody.Loc.Country == "": + return false + + } + + if distributorName == nil || *distributorName == "" { + return false + } + + *distributorName = strings.ToUpper(*distributorName) + + distributor := s.store.GetDistributorByName(*distributorName) + if distributor == nil { + return false + } + isIncludeLoc := checkPermission(distributor.Include, *reqBody.Loc) + if !isIncludeLoc { + // return false - as it does have have permission to distribute + + return false + } + + // check for exclude permission + for { + isExcludeLoc := checkPermission(distributor.Exclude, *reqBody.Loc) + if isExcludeLoc { + + return false + } + + // check for exlude permission + if distributor.ParentDistributor != nil { + distributor = s.store.GetDistributorByName(strings.ToUpper(*distributor.ParentDistributor)) + } else { + break + } + } + + return true +} + +func checkPermission(distributorLoc []models.Location, loc models.Location) bool { + for _, dLoc := range distributorLoc { + if strings.EqualFold(dLoc.Country, loc.Country) { + if strings.EqualFold(dLoc.Province, loc.Province) || strings.EqualFold(dLoc.Province, "ALL") { + if strings.EqualFold(dLoc.City, loc.City) || strings.EqualFold(dLoc.City, "ALL") { + return true + } + } + } + } + + return false +} + +func (s *service) AutoInitialiseFields(loc []models.Location) []models.Location { + for i := range loc { + + if loc[i].City != "" { + loc[i] = *s.store.GetLocationDetailsByCity(loc[i].City) + } else if loc[i].Province != "" { + + loc[i] = *s.store.GetLocationDetailsByProvince(loc[i].Province) + } else if loc[i].Country != "" { + + loc[i] = *s.store.GetLocationDetailsByCountry(loc[i].Country) + } + } + + return loc +} diff --git a/internal/store/interface.go b/internal/store/interface.go new file mode 100644 index 000000000..171a705b6 --- /dev/null +++ b/internal/store/interface.go @@ -0,0 +1,11 @@ +package store + +import "challange2016/internal/models" + +type Store interface { + AddDistributor(obj *models.Distributor) *models.Distributor + GetDistributorByName(distributorName string) *models.Distributor + GetLocationDetailsByCity(cityName string) *models.Location + GetLocationDetailsByProvince(province string) *models.Location + GetLocationDetailsByCountry(countryName string) *models.Location +} diff --git a/internal/store/store.go b/internal/store/store.go new file mode 100644 index 000000000..3768809bb --- /dev/null +++ b/internal/store/store.go @@ -0,0 +1,67 @@ +package store + +import ( + "challange2016/internal/models" + "strings" +) + +type store struct { + dMap *models.DistributionMaps +} + +func NewStore(dMap *models.DistributionMaps) *store { + return &store{ + dMap: dMap, + } +} + +func (s *store) AddDistributor(obj *models.Distributor) *models.Distributor { + // storing a distributor in a map + s.dMap.Distributor[obj.Name] = obj + + // get call + + return s.GetDistributorByName(obj.Name) +} + +func (s *store) GetDistributorByName(distributorName string) *models.Distributor { + distributorName = strings.ToUpper(distributorName) + + distributor, ok := s.dMap.Distributor[distributorName] + if !ok { + return nil + } + + return distributor +} + +func (s *store) GetLocationDetailsByCity(cityName string) *models.Location { + cityName = strings.ToUpper(cityName) + + loc, ok := s.dMap.CityMap[cityName] + if !ok { + return nil + } + + return loc +} + +func (s *store) GetLocationDetailsByProvince(province string) *models.Location { + province = strings.ToUpper(province) + loc, ok := s.dMap.ProvinceMap[province] + if !ok { + return nil + } + return loc +} + +func (s *store) GetLocationDetailsByCountry(countryName string) *models.Location { + countryName = strings.ToUpper(countryName) + + loc, ok := s.dMap.CountryMap[countryName] + if !ok { + return nil + } + + return loc +} diff --git a/internal/utils/csvreader.go b/internal/utils/csvreader.go new file mode 100644 index 000000000..535cdd858 --- /dev/null +++ b/internal/utils/csvreader.go @@ -0,0 +1,80 @@ +package utils + +import ( + "challange2016/internal/models" + "encoding/csv" + "io" + "log" + "os" + "strings" +) + +func LoadCsvIntoLocalStores(storeMap *models.DistributionMaps, fileName string) error { + + // reading the csv file + reader, err := os.OpenFile(fileName, os.O_RDONLY, 0777) + if err != nil { + log.Println("Error in opening file, Err", err) + return err + } + + csvReader := csv.NewReader(reader) + + for { + record, err := csvReader.Read() + if err == io.EOF { + break + } + + if err != nil { + log.Printf("Err :%v", err) + return err + } + + if len(record) != 6 { + log.Println("Record length is less than 6") + } + + loc := models.Location{ + CityCode: record[0], + ProvinceCode: record[1], + CountryCode: record[2], + City: record[3], + Province: record[4], + Country: record[5], + } + + //was not settting it prooperly + // storeMap.CityMap[strings.ToUpper(loc.City)] = &loc + // loc.City = "ALL" + // loc.CityCode = "ALL" + // storeMap.ProvinceMap[strings.ToUpper(loc.Province)] = &loc + // loc.Province = "ALL" + // loc.ProvinceCode = "ALL" + // storeMap.CountryMap[strings.ToUpper(loc.Country)] = &loc + setCityMap(storeMap, loc) + setProvinceMap(storeMap, loc) + setCountryMap(storeMap, loc) + + } + + return nil +} + +func setCityMap(dMap *models.DistributionMaps, loc models.Location) { + dMap.CityMap[strings.ToUpper(loc.City)] = &loc +} + +func setProvinceMap(dMap *models.DistributionMaps, loc models.Location) { + loc.City = "ALL" + loc.CityCode = "ALL" + dMap.ProvinceMap[strings.ToUpper(loc.Province)] = &loc +} + +func setCountryMap(dMap *models.DistributionMaps, loc models.Location) { + loc.City = "ALL" + loc.CityCode = "ALL" + loc.Province = "ALL" + loc.ProvinceCode = "ALL" + dMap.CountryMap[strings.ToUpper(loc.Country)] = &loc +} diff --git a/main.go b/main.go new file mode 100644 index 000000000..bdf082116 --- /dev/null +++ b/main.go @@ -0,0 +1,198 @@ +package main + +import ( + "bufio" + "challange2016/internal/models" + "challange2016/internal/service" + "challange2016/internal/store" + "challange2016/internal/utils" + "fmt" + "log" + "os" + "strings" +) + +func main() { + + storeMap := models.NewDistributionMaps() + + //pushing daat to cache + utils.LoadCsvIntoLocalStores(storeMap, "cities.csv") + + store := store.NewStore(storeMap) + svc := service.New(store) + + //cli inputs + scanner := bufio.NewScanner(os.Stdin) + + for { + fmt.Println("Choose an option:") + fmt.Println("1. Add new permission") + fmt.Println("2. Get Distributor With Name") + fmt.Println("3. Check Distributor Permission") + fmt.Println("4. Exit") + + var option int + _, err := fmt.Scan(&option) + if err != nil { + fmt.Println("Error reading option:", err) + continue + } + scanner.Scan() + switch option { + case 1: + fmt.Println("reached check one") + var dist string + //var permissionType string + //var input string + + fmt.Print("Enter distributor: ") + scanner.Scan() // This waits for the user input + dist = scanner.Text() + dist = strings.TrimSpace(dist) + var distributorobj models.Distributor + if strings.Contains(dist, "<") { + dList := strings.Split(dist, "<") + + distributorobj.Name = dList[0] + distributorobj.ParentDistributor = &dList[1] + + } else { + distributorobj.Name = dist + + } + + var includes []models.Location + var excludes []models.Location + for { + fmt.Println("Enter Inclisions and Exclusions : ") + fmt.Println("Enter Exit to exit") + var permission string + scanner.Scan() // This waits for the user input + permission = scanner.Text() + permission = strings.TrimSpace(permission) + if strings.ToUpper(permission) == "EXIT" { + distributorobj.Include = includes + distributorobj.Exclude = excludes + break + } + partspermission := strings.Split(permission, ":") + if len(partspermission) != 2 { + fmt.Println("Invalid input format. Please use the format 'INCLUDE: COUNTRY-PROVINCE-CITY EXCLUDE: COUNTRY-PROVINCE-CITY'.") + continue + } + permissionTypeStr := strings.TrimSpace(partspermission[0]) + input := strings.TrimSpace(partspermission[1]) + parts := strings.Split(input, "-") + if len(parts) == 1 { + fmt.Println("checking pas throgugh one") + country := strings.ToUpper(parts[0]) + tempLocation := models.Location{ + Country: country, + } + + if permissionTypeStr == "INCLUDE" { + fmt.Println("checking pas throgugh one") + includes = append(includes, tempLocation) + } else if permissionTypeStr == "EXCLUDE" { + excludes = append(excludes, tempLocation) + } + + } else if len(parts) == 2 { + province, country := strings.ToUpper(parts[0]), strings.ToUpper(parts[1]) + tempLocation := models.Location{ + Country: country, + Province: province, + } + + if permissionTypeStr == "INCLUDE" { + includes = append(includes, tempLocation) + } else if permissionTypeStr == "EXCLUDE" { + excludes = append(excludes, tempLocation) + log.Println("check", excludes) + } + + } else if len(parts) == 3 { + city, province, country := strings.ToUpper(parts[0]), strings.ToUpper(parts[1]), strings.ToUpper(parts[2]) + tempLocation := models.Location{ + Country: country, + Province: province, + City: city, + } + + if permissionTypeStr == "INCLUDE" { + includes = append(includes, tempLocation) + } else if permissionTypeStr == "EXCLUDE" { + excludes = append(excludes, tempLocation) + } + } else { + fmt.Println("Invalid String. Please enter this way country, province-country, city-province-country") + continue + } + + } + fmt.Println("obj") + response, err := svc.AddDistributor(&distributorobj) + if err != nil { + fmt.Println(err) + } + fmt.Println("checking pas throgugh seven") + fmt.Println("response", response) + case 2: + var distributorName string + _, err := fmt.Scan(&distributorName) + if err != nil { + fmt.Println("Error reading option:", err) + continue + } + distributor, err := svc.GetDistributorByName(&distributorName) + if err != nil && err.Error() == "entity not found" { + return + } + fmt.Println("distributor name", distributor) + case 3: + var distributorName string + _, err := fmt.Scan(&distributorName) + if err != nil { + fmt.Println("Error reading option:", err) + + } + //distributor, _ := svc.GetDistributorByName(&distributorName) + + bufio.NewReader(os.Stdin).ReadString('\n') + fmt.Println("Enter Distributor Name to Check Permissions") + scanner := bufio.NewScanner(os.Stdin) + //fmt.Println("distributor name", distributor) + + var permission string + scanner.Scan() // This waits for the user input + permission = scanner.Text() + permission = strings.TrimSpace(permission) + partspermission := strings.Split(permission, ":") + if len(partspermission) != 2 { + fmt.Println("Invalid input format. Please use the format 'NAME: City-PROVINCE-Country.") + continue + } + name := partspermission[0] + parts := strings.Split(partspermission[1], "-") + if len(parts) != 3 { + fmt.Println("invallid input format") + break + } + var loc models.Location + + loc.City = parts[0] + loc.Province = parts[1] + loc.Country = parts[2] + var checkPermission models.CheckPermission + checkPermission.DistributorName = &name + checkPermission.Loc = &loc + response := svc.CheckDistributorPermission(&distributorName, checkPermission) + fmt.Println("Can Distribute", response) + case 4: + os.Exit(0) + default: + fmt.Println("Invalid option. Please choose 1, 2, or 3.") + } + } +}