Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Challenge 2016 #155

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module movie-distribution

go 1.21.6
53 changes: 53 additions & 0 deletions handlers/handler.go
Original file line number Diff line number Diff line change
@@ -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)
}
46 changes: 46 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -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))
}
51 changes: 51 additions & 0 deletions models/csv_loader.go
Original file line number Diff line number Diff line change
@@ -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
}
59 changes: 59 additions & 0 deletions models/distributors.go
Original file line number Diff line number Diff line change
@@ -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
}
57 changes: 57 additions & 0 deletions models/location.go
Original file line number Diff line number Diff line change
@@ -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()
13 changes: 13 additions & 0 deletions models/permissions.go
Original file line number Diff line number Diff line change
@@ -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,
}
}
27 changes: 27 additions & 0 deletions models/regions.go
Original file line number Diff line number Diff line change
@@ -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
}
38 changes: 38 additions & 0 deletions static/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Movie Distribution Permission Checker</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="container">
<h1>Movie Distribution Permission Checker</h1>

<div class="form-container">
<div class="form-group">
<label for="distributor">Distributor:</label>
<select id="distributor">
<option value="DISTRIBUTOR1">DISTRIBUTOR1</option>
<option value="DISTRIBUTOR2">DISTRIBUTOR2</option>
<option value="DISTRIBUTOR3">DISTRIBUTOR3</option>
</select>
</div>

<div class="form-group">
<label for="region">Region Code:</label>
<input type="text" id="region" placeholder="e.g., CHI-IL-USA" />
</div>

<button onclick="checkPermission()">Check Permission</button>
</div>

<div id="result" class="result hidden">
<h2>Result:</h2>
<p id="resultText"></p>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
40 changes: 40 additions & 0 deletions static/script.js
Original file line number Diff line number Diff line change
@@ -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");
}
}
Loading