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 @@ + + + + + + Movie Distribution Permission Checker + + + +
+

Movie Distribution Permission Checker

+ +
+
+ + +
+ +
+ + +
+ + +
+ + +
+ + + diff --git a/static/script.js b/static/script.js new file mode 100644 index 000000000..d8a9d723b --- /dev/null +++ b/static/script.js @@ -0,0 +1,40 @@ +async function checkPermission() { + const distributor = document.getElementById("distributor").value; + const region = document.getElementById("region").value; + const resultDiv = document.getElementById("result"); + const resultText = document.getElementById("resultText"); + + if (!region) { + alert("Please enter a region code"); + return; + } + + try { + const response = await fetch("/check-permission", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + distributorName: distributor, + region: region, + }), + }); + + if (!response.ok) { + throw new Error("Network response was not ok"); + } + + const data = await response.json(); + + resultDiv.classList.remove("hidden", "allowed", "denied"); + resultDiv.classList.add(data.hasPermission ? "allowed" : "denied"); + + resultText.textContent = data.hasPermission + ? `${distributor} is ALLOWED to distribute in ${region}` + : `${distributor} is NOT ALLOWED to distribute in ${region}`; + } catch (error) { + console.error("Error:", error); + alert("An error occurred while checking the permission"); + } +} diff --git a/static/style.css b/static/style.css new file mode 100644 index 000000000..45be96a42 --- /dev/null +++ b/static/style.css @@ -0,0 +1,85 @@ +body { + font-family: Arial, sans-serif; + line-height: 1.6; + margin: 0; + padding: 20px; + background-color: #f5f5f5; +} + +.container { + max-width: 800px; + margin: 0 auto; + background-color: white; + padding: 20px; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +h1 { + text-align: center; + color: #333; + margin-bottom: 30px; +} + +.form-container { + display: flex; + flex-direction: column; + gap: 20px; + margin-bottom: 30px; +} + +.form-group { + display: flex; + flex-direction: column; + gap: 8px; +} + +label { + font-weight: bold; + color: #555; +} + +input, +select { + padding: 8px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 16px; +} + +button { + padding: 12px; + background-color: #007bff; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 16px; + transition: background-color 0.2s; +} + +button:hover { + background-color: #0056b3; +} + +.result { + margin-top: 20px; + padding: 20px; + border-radius: 4px; +} + +.result.hidden { + display: none; +} + +.allowed { + background-color: #d4edda; + color: #155724; + border: 1px solid #c3e6cb; +} + +.denied { + background-color: #f8d7da; + color: #721c24; + border: 1px solid #f5c6cb; +}