Skip to content
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
distrib
current_state.json
17 changes: 12 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,19 @@ INCLUDE: HUBLI-KARNATAKA-INDIA
```
Again, `DISTRIBUTOR2` cannot authorize `DISTRIBUTOR3` with a region that they themselves do not have access to.

We've provided a CSV with the list of all countries, states and cities in the world that we know of - please use the data mentioned there for this program. *The codes you see there may be different from what you see here, so please always use the codes in the CSV*. This Readme is only an example.
## Steps to run

Write a program in any language you want (If you're here from Gophercon, use Go :D) that does this. Feel free to make your own input and output format / command line tool / GUI / Webservice / whatever you want. Feel free to hold the dataset in whatever structure you want, but try not to use external databases - as far as possible stick to your langauage without bringing in MySQL/Postgres/MongoDB/Redis/Etc.

To submit a solution, fork this repo and send a Pull Request on Github.
```
make run
```

For any questions or clarifications, raise an issue on this repo and we'll answer your questions as fast as we can.
1. Add distributor - `./distrib add distributor1`
2. To add permissions for a distributor - `./distrib include distributor1 INDIA`
3. To check if distributor has permission - `./distrib check distributor1 ONGOLE-ANDHRAPRADESH-INDIA`
4. To list all distributors - `./distrib list`
5. To exclude a city for a distributor - `./distrib exclude distributor1 TAMILNADU-INDIA`
6. To show distributor information - `./distrib show distributor1`
7. To copy permissions from a distributor - `./distrib add distributor2 --parent distributor1`


We store the current state in `current_state.json`. If we want to reset all permissions, just remove or delete the json file.
25 changes: 25 additions & 0 deletions cmd/app/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package main

import (
"fmt"
"os"

"challenge/cmd/commands"
)

func main() {
config := &commands.Config{
StateFile: "current_state.json",
CitiesFile: "cities.csv",
LogLevel: "info",
}

logger := commands.InitLogger(config.LogLevel)
app := commands.NewApp(logger, config)
rootCmd := app.BuildRootCommand()
if err := rootCmd.Execute(); err != nil {
logger.Error("command execution failed", "error", err.Error())
fmt.Println(err)
os.Exit(1)
}
}
277 changes: 277 additions & 0 deletions cmd/commands/commands.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
package commands

import (
"fmt"
"log/slog"
"os"
"strings"

"challenge/pkg/services"

"challenge/pkg/utils"

"github.com/spf13/cobra"
)

type Config struct {
StateFile string
CitiesFile string
LogLevel string
LogFormat string
}

type App struct {
service *services.DistributionService
jsonRepo *utils.JSONUtil
csvRepo *utils.CSVUtil
config *Config
logger *slog.Logger
}

func NewApp(logger *slog.Logger, config *Config) *App {
return &App{
service: services.NewDistributionService(logger),
jsonRepo: utils.NewJSONUtil(logger),
csvRepo: utils.NewCSVUtil(logger),
config: config,
logger: logger,
}
}

func InitLogger(level string) *slog.Logger {
var logLevel slog.Level
switch strings.ToLower(level) {
case "debug":
logLevel = slog.LevelDebug
case "info":
logLevel = slog.LevelInfo
case "warn":
logLevel = slog.LevelWarn
case "error":
logLevel = slog.LevelError
default:
logLevel = slog.LevelInfo
}

opts := &slog.HandlerOptions{
Level: logLevel,
}

handler := slog.NewJSONHandler(os.Stdout, opts)

return slog.New(handler)
}

// LoadData loads cities and state
func (a *App) LoadData() error {
a.logger.Info("starting distribution system",
slog.String("version", "1.0.0"),
slog.String("log_level", a.config.LogLevel),
slog.String("log_format", a.config.LogFormat))

// Load cities if file exists
if a.config.CitiesFile != "" {
if _, err := os.Stat(a.config.CitiesFile); err == nil {
locations, err := a.csvRepo.LoadCities(a.config.CitiesFile)
if err != nil {
a.logger.Warn("could not load cities file",
slog.String("error", err.Error()))
} else {
a.service.SetLocations(locations)
}
} else {
a.logger.Debug("cities file not found, skipping",
slog.String("filename", a.config.CitiesFile))
}
}

// Load state if file exists
if a.config.StateFile != "" {
if a.jsonRepo.Exists(a.config.StateFile) {
distributors, err := a.jsonRepo.Load(a.config.StateFile)
if err != nil {
a.logger.Warn("could not load state file",
slog.String("error", err.Error()))
} else {
a.service.SetDistributors(distributors)
}
} else {
a.logger.Debug("state file not found, starting fresh",
slog.String("filename", a.config.StateFile))
}
}

return nil
}

func (a *App) SaveData() error {
if a.config.StateFile != "" {
return a.jsonRepo.Save(a.config.StateFile, a.service.GetDistributors())
}
return nil
}

func (a *App) BuildRootCommand() *cobra.Command {
rootCmd := &cobra.Command{
Use: "distrib",
Short: "Distribution Permission Management System",
Long: "A CLI tool to manage movie distribution permissions across geographical territories",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
a.LoadData()
},
PersistentPostRun: func(cmd *cobra.Command, args []string) {
if err := a.SaveData(); err != nil {
a.logger.Error("failed to save state",
slog.String("error", err.Error()))
}
},
}

rootCmd.AddCommand(
a.buildAddCommand(),
a.buildIncludeCommand(),
a.buildExcludeCommand(),
a.buildCheckCommand(),
a.buildShowCommand(),
a.buildListCommand(),
)

return rootCmd
}

func (a *App) buildAddCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "add [distributor-name]",
Short: "Add a new distributor",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
parent, _ := cmd.Flags().GetString("parent")
err := a.service.AddDistributor(args[0], parent)
if err != nil {
fmt.Printf("Error: %v\n", err)
os.Exit(1)
}
fmt.Printf("Added distributor: %s\n", args[0])
if parent != "" {
fmt.Printf("Parent: %s\n", parent)
}
},
}
cmd.Flags().StringP("parent", "p", "", "Parent distributor")
return cmd
}

func (a *App) buildIncludeCommand() *cobra.Command {
return &cobra.Command{
Use: "include [distributor] [location]",
Short: "Add an include permission for a distributor",
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
err := a.service.AddPermission(args[0], true, args[1])
if err != nil {
fmt.Printf("Error: %v\n", err)
os.Exit(1)
}
fmt.Printf("Added INCLUDE permission for %s: %s\n", args[0], args[1])
},
}
}

func (a *App) buildExcludeCommand() *cobra.Command {
return &cobra.Command{
Use: "exclude [distributor] [location]",
Short: "Add an exclude permission for a distributor",
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
err := a.service.AddPermission(args[0], false, args[1])
if err != nil {
fmt.Printf("Error: %v\n", err)
os.Exit(1)
}
fmt.Printf("Added EXCLUDE permission for %s: %s\n", args[0], args[1])
},
}
}

func (a *App) buildCheckCommand() *cobra.Command {
return &cobra.Command{
Use: "check [distributor] [location]",
Short: "Check if a distributor can distribute in a location",
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
can, err := a.service.CanDistribute(args[0], args[1])
if err != nil {
fmt.Printf("Error: %v\n", err)
os.Exit(1)
}
if can {
fmt.Println("Yes can distribute")
} else {
fmt.Println("Can't distribute")
}
},
}
}

func (a *App) buildShowCommand() *cobra.Command {
return &cobra.Command{
Use: "show [distributor]",
Short: "Show distributor information",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
dist, err := a.service.GetDistributor(args[0])
if err != nil {
fmt.Printf("Distributor %s not found\n", args[0])
os.Exit(1)
}

fmt.Printf("\n┌─ Distributor: %s\n", dist.Name)
if dist.Parent != "" {
fmt.Printf("├─ Parent: %s\n", dist.Parent)
}
fmt.Printf("├─ Permissions:\n")

if len(dist.Permissions) == 0 {
fmt.Printf("│ (none)\n")
} else {
for i, perm := range dist.Permissions {
prefix := "├──"
if i == len(dist.Permissions)-1 {
prefix = "└──"
}

permType := "EXCLUDE"
if perm.IsInclude {
permType = "INCLUDE"
}
fmt.Printf("│ %s %s: %s\n", prefix, permType, perm.Location.String())
}
}
fmt.Println()
},
}
}

func (a *App) buildListCommand() *cobra.Command {
return &cobra.Command{
Use: "list",
Short: "List all distributors",
Run: func(cmd *cobra.Command, args []string) {
distributors := a.service.GetDistributors()
if len(distributors) == 0 {
fmt.Println("No distributors found")
return
}

fmt.Println("\nDistributors:")
for name, dist := range distributors {
parent := "root"
if dist.Parent != "" {
parent = dist.Parent
}
fmt.Printf(" • %s (parent: %s, permissions: %d)\n", name, parent, len(dist.Permissions))
}
fmt.Println()
},
}
}
10 changes: 10 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module challenge

go 1.22.12

require github.com/spf13/cobra v1.10.2

require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/pflag v1.0.9 // indirect
)
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
3 changes: 3 additions & 0 deletions makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
run:
CGO_ENABLED=0 go build -o distrib ./cmd/app/
./distrib
40 changes: 40 additions & 0 deletions pkg/models/models.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package models

type Location struct {
City string `json:"city,omitempty"`
State string `json:"state,omitempty"`
Country string `json:"country"`
}

type Permission struct {
IsInclude bool `json:"is_include"`
Location Location `json:"location"`
}

type Distributor struct {
Name string `json:"name"`
Parent string `json:"parent,omitempty"`
Permissions []Permission `json:"permissions"`
}

func (l Location) String() string {
if l.City != "" {
return l.City + "-" + l.State + "-" + l.Country
}
if l.State != "" {
return l.State + "-" + l.Country
}
return l.Country
}

func (l Location) IsCountryLevel() bool {
return l.City == "" && l.State == ""
}

func (l Location) IsStateLevel() bool {
return l.City == "" && l.State != ""
}

func (l Location) IsCityLevel() bool {
return l.City != ""
}
Loading