diff --git a/go.mod b/go.mod new file mode 100644 index 000000000..e5d450b4b --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module movie-distribution + +go 1.21.6 diff --git a/handlers/handler.go b/handlers/handler.go new file mode 100644 index 000000000..4ae8d8c9b --- /dev/null +++ b/handlers/handler.go @@ -0,0 +1,53 @@ +package handlers + +import ( + "encoding/json" + "net/http" + + "movie-distribution/models" +) + +type Handler struct { + distributors map[string]*models.Distributor +} + +func NewHandler(distributors map[string]*models.Distributor) *Handler { + return &Handler{ + distributors: distributors, + } +} + +type CheckPermissionRequest struct { + DistributorName string `json:"distributorName"` + Region string `json:"region"` +} + +type CheckPermissionResponse struct { + HasPermission bool `json:"hasPermission"` +} + +func (h *Handler) CheckPermission(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + var req CheckPermissionRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + distributor, exists := h.distributors[req.DistributorName] + if !exists { + http.Error(w, "Distributor not found", http.StatusNotFound) + return + } + + result := CheckPermissionResponse{ + HasPermission: distributor.HasPermission(req.Region), + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(result) +} diff --git a/main.go b/main.go new file mode 100644 index 000000000..2f0864489 --- /dev/null +++ b/main.go @@ -0,0 +1,46 @@ +package main + +import ( + "log" + "net/http" + + "movie-distribution/handlers" + "movie-distribution/models" +) + +func main() { + // Load locations from CSV + err := models.LoadLocationsFromCSV("cities.csv") + if err != nil { + log.Fatalf("Failed to load locations: %v", err) + } + + // Initialize distributors + d1 := models.NewDistributor("DISTRIBUTOR1", nil) + d1.AddPermission("INCLUDE", "IN") // India + d1.AddPermission("INCLUDE", "US") // United States + d1.AddPermission("EXCLUDE", "KA-IN") // Karnataka, India + d1.AddPermission("EXCLUDE", "CHE-TN-IN") // Chennai, Tamil Nadu, India + + d2 := models.NewDistributor("DISTRIBUTOR2", d1) + d2.AddPermission("INCLUDE", "IN") + d2.AddPermission("EXCLUDE", "TN-IN") + + d3 := models.NewDistributor("DISTRIBUTOR3", d2) + d3.AddPermission("INCLUDE", "HBL-KA-IN") + + // Initialize handler with distributors + h := handlers.NewHandler(map[string]*models.Distributor{ + "DISTRIBUTOR1": d1, + "DISTRIBUTOR2": d2, + "DISTRIBUTOR3": d3, + }) + + // Set up routes + http.Handle("/", http.FileServer(http.Dir("static"))) + http.HandleFunc("/check-permission", h.CheckPermission) + + // Start server + log.Println("Server starting on http://localhost:8080") + log.Fatal(http.ListenAndServe(":8080", nil)) +} diff --git a/models/csv_loader.go b/models/csv_loader.go new file mode 100644 index 000000000..64bb06026 --- /dev/null +++ b/models/csv_loader.go @@ -0,0 +1,51 @@ +package models + +import ( + "encoding/csv" + "io" + "os" +) + +func LoadLocationsFromCSV(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 + } + + // CSV format: City Code, Province Code, Country Code, City Name, Province Name, Country Name + cityCode := record[0] + provinceCode := record[1] + countryCode := record[2] + cityName := record[3] + provinceName := record[4] + countryName := record[5] + + GlobalLocationDB.AddLocation( + cityCode, + provinceCode, + countryCode, + cityName, + provinceName, + countryName, + ) + } + + return nil +} diff --git a/models/distributors.go b/models/distributors.go new file mode 100644 index 000000000..e7c02aed0 --- /dev/null +++ b/models/distributors.go @@ -0,0 +1,59 @@ +package models + +type Distributor struct { + Name string + Parent *Distributor + Permissions []Permission +} + +func NewDistributor(name string, parent *Distributor) *Distributor { + return &Distributor{ + Name: name, + Parent: parent, + Permissions: make([]Permission, 0), + } +} + +func (d *Distributor) AddPermission(permType, region string) { + d.Permissions = append(d.Permissions, NewPermission(permType, region)) +} + +func (d *Distributor) HasPermission(region string) bool { + // Check parent permissions first + if d.Parent != nil && !d.Parent.HasPermission(region) { + return false + } + + targetRegion := NewRegion(region) + allowed := false + + // Process includes first + for _, perm := range d.Permissions { + if perm.Type == "INCLUDE" { + permRegion := NewRegion(perm.Region) + if permRegion.Contains(targetRegion) { + allowed = true + break + } + } + } + + // If not included by this distributor but has parent, check parent's includes + if !allowed && d.Parent != nil { + allowed = d.Parent.HasPermission(region) + } + + // Process excludes + if allowed { + for _, perm := range d.Permissions { + if perm.Type == "EXCLUDE" { + permRegion := NewRegion(perm.Region) + if permRegion.Contains(targetRegion) { + return false + } + } + } + } + + return allowed +} diff --git a/models/location.go b/models/location.go new file mode 100644 index 000000000..a1dcf5c93 --- /dev/null +++ b/models/location.go @@ -0,0 +1,57 @@ +package models + +type Location struct { + CityCode string + ProvinceCode string + CountryCode string + CityName string + ProvinceName string + CountryName string +} + +type LocationDB struct { + Locations map[string]*Location +} + +func NewLocationDB() *LocationDB { + return &LocationDB{ + Locations: make(map[string]*Location), + } +} + +func (db *LocationDB) AddLocation(cityCode, provinceCode, countryCode, cityName, provinceName, countryName string) { + // Create composite keys for different levels + countryKey := countryCode + provinceKey := provinceCode + "-" + countryCode + cityKey := cityCode + "-" + provinceCode + "-" + countryCode + + // Add all levels to the locations map + db.Locations[countryKey] = &Location{ + CountryCode: countryCode, + CountryName: countryName, + } + + db.Locations[provinceKey] = &Location{ + ProvinceCode: provinceCode, + CountryCode: countryCode, + ProvinceName: provinceName, + CountryName: countryName, + } + + db.Locations[cityKey] = &Location{ + CityCode: cityCode, + ProvinceCode: provinceCode, + CountryCode: countryCode, + CityName: cityName, + ProvinceName: provinceName, + CountryName: countryName, + } +} + +func (db *LocationDB) IsValidRegion(code string) bool { + _, exists := db.Locations[code] + return exists +} + +// Global instance +var GlobalLocationDB = NewLocationDB() diff --git a/models/permissions.go b/models/permissions.go new file mode 100644 index 000000000..2876183da --- /dev/null +++ b/models/permissions.go @@ -0,0 +1,13 @@ +package models + +type Permission struct { + Type string + Region string +} + +func NewPermission(permType, region string) Permission { + return Permission{ + Type: permType, + Region: region, + } +} diff --git a/models/regions.go b/models/regions.go new file mode 100644 index 000000000..7f98acb89 --- /dev/null +++ b/models/regions.go @@ -0,0 +1,27 @@ +package models + +import "strings" + +type Region struct { + Code string +} + +func NewRegion(code string) *Region { + return &Region{Code: code} +} + +func (r *Region) Contains(other *Region) bool { + rParts := strings.Split(r.Code, "-") + oParts := strings.Split(other.Code, "-") + + if len(rParts) > len(oParts) { + return false + } + + for i := 0; i < len(rParts); i++ { + if rParts[i] != oParts[i] { + return false + } + } + return true +} diff --git a/static/index.html b/static/index.html new file mode 100644 index 000000000..248dbfed1 --- /dev/null +++ b/static/index.html @@ -0,0 +1,38 @@ + + +
+ + +