diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..67a85520e87ea07fe850898c49e8e2d17f048066 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +json-utility diff --git a/Dockerfile b/Dockerfile index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0310e9fbba9d3c113a2a53916c09b450a5cf2ac7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -0,0 +1,5 @@ +FROM golang:1.16.2-buster +WORKDIR . +COPY . . +RUN make build +RUN make build file=example.json \ No newline at end of file diff --git a/Makefile b/Makefile index ff05f2fd8bd902ff1bbb64d5c0bdc1468492873f..432a791044157129c35edd891e8c0550078107f0 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ build: go get run: - go run . $(file) + go run . $(mode) $(file) test: go test \ No newline at end of file diff --git a/README.md b/README.md index 72096c98737a2e374d28cf138eff2f3a5fbf24d6..3fc22dc4939ad77cee9008bd420414f96339051f 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,17 @@ ## Usage ``` $ make build -$ make run file=example.json #where example.json is the file we want to process +$ make run mode=TEST file=example.json #TESTING WITH LOCAL FILE. Where example.json is the file we want to process +$ make run mode=CONTAINER #RUN AS REST API. ``` ## Testing ``` $ make test ``` +## Discussion +- Designing the solution is made based on the assumption that the load will be a +JSON file containing an array of all the entries needed to perform the calculations. ## Dependencies - You'd need to have GOPATH exported. GOBIN for MACOS for go get command \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/handler.go b/handler.go deleted file mode 100644 index b7a9b9bd8d4f7aee4ee94b328b0e937b278eb54d..0000000000000000000000000000000000000000 --- a/handler.go +++ /dev/null @@ -1,58 +0,0 @@ -package main - -import ( - "encoding/json" - "log" - "os" - "sync" -) - -//Handles opens the JSON file and calls extractJSON to parse -//the data. Returns the result as a struct. -func Handles(argument string) (urls *URLs, err error) { - jsonFile, err := os.Open(argument) - if err != nil { - log.Println(err) - return nil, err - } - log.Println("Successfully Opened", argument) - //Close when done. - defer jsonFile.Close() - result := ExctractJSON(jsonFile) - return result, err -} - -func PrintResult(result *URLs) { - for k, v := range result.URL { - log.Println("For URL: ", k) - for m, x := range v.Date { - log.Println("↳", m, "there are:", x.Counter, "unique visits.") - } - } -} - -func ExctractJSON(file *os.File) (urls *URLs) { - d := json.NewDecoder(file) - - var loads []Load - visited := URLs{URL: make(map[string]*Dates)} - - err := d.Decode(&loads) - if err != nil { - log.Panic(err) - return - } - wg := new(sync.WaitGroup) - - for _, load := range loads { - if load.TypeRequest != "GET" { - log.Println("Request is invalid: ", load.TypeRequest) - continue - } - wg.Add(1) - go Process(&visited, load, wg) - - } - wg.Wait() - return &visited -} diff --git a/handlers.go b/handlers.go new file mode 100644 index 0000000000000000000000000000000000000000..0ff70b0d22719e2997c3b5a10a3f35a24cf9a851 --- /dev/null +++ b/handlers.go @@ -0,0 +1,58 @@ +package main + +import ( + "encoding/json" + "log" + "net/http" + "os" + "sync" +) + +//Handles opens the JSON file and calls extractJSON to parse +//the data passed as file. Returns the result as a struct. +func Handles(argument string) (urls *Links, err error) { + jsonFile, err := os.Open(argument) + if err != nil { + log.Println(err) + return nil, err + } + log.Println("Successfully Opened", argument) + //Close when done. + defer jsonFile.Close() + payload := ExctractJSON(jsonFile) + result := ProcessJson(payload) + return result, err +} + +//Web handler for requests containing JSON input. +func Index(w http.ResponseWriter, r *http.Request) { + decoder := json.NewDecoder(r.Body) + var loads []Load + + err := decoder.Decode(&loads) + if err != nil { + panic(err) + } + + result := ProcessJson(loads) + WriteResult(w, result) +} + +//Preocessing the JSON as a whole array. +func ProcessJson(loads []Load) (links *Links) { + visited := Links{URL: make(map[string]*Dates)} + + wg := new(sync.WaitGroup) + + for _, load := range loads { + if load.TypeRequest != "GET" { + log.Println("Request is invalid: ", load.TypeRequest) + continue + } + wg.Add(1) + go ProcessLoad(&visited, load, wg) + + } + wg.Wait() + return &visited +} diff --git a/handlertest.go b/handlerstest.go similarity index 100% rename from handlertest.go rename to handlerstest.go diff --git a/json-utility b/json-utility index 7016e122e48334e254edbffd295222cb94d05229..0347b29c76a60301c422e545efda95c73a1d895d 100755 Binary files a/json-utility and b/json-utility differ diff --git a/main.go b/main.go index c336ddadf774bab85e26f61cfed4acf3291105e0..2182a257528aeacdb0d8b5b1cdff1b77c1a4ab8b 100644 --- a/main.go +++ b/main.go @@ -4,8 +4,11 @@ package main import ( "log" + "net/http" "os" "sync" + + "github.com/gorilla/mux" ) type Load struct { @@ -15,7 +18,7 @@ type Load struct { Timestamp int64 `json:"timestamp"` } -type URLs struct { +type Links struct { URL map[string]*Dates //map holding all the Dates a certain URL is visited mux sync.RWMutex //mutex for race condition } @@ -30,17 +33,26 @@ type Visits struct { } func main() { - //Check if the usage of the script has been correct. - if len(os.Args) != 2 { - log.Println("USAGE: ./json-utility [path/to/file]") - return - } - //Get the result from a file. - result, err := Handles(os.Args[1]) - if err != nil { - log.Println(err) - return + if os.Args[1] == "CONTAINER" { + router := mux.NewRouter().StrictSlash(true) + router.HandleFunc("/", Index) + log.Fatal(http.ListenAndServe(":8080", router)) + } else { + //Check if the usage of the script has been correct. + if len(os.Args) != 3 { + log.Println("USAGE: ./json-utility [mode] [path/to/file]") + return + } + + //Get the result from a file. + result, err := Handles(os.Args[2]) + if err != nil { + log.Println(err) + return + } + + //Print the result + PrintResult(result) } - //Print the result - PrintResult(result) + } diff --git a/process.go b/process.go index 13ab0545c95e994dd47ce61d74589402c6c1ed10..30f970d0fddbe7d36bf045b2726db244391afc3e 100644 --- a/process.go +++ b/process.go @@ -7,7 +7,7 @@ import ( ) //Process handles processing a load given by -func Process(visited *URLs, load Load, wg *sync.WaitGroup) { +func ProcessLoad(visited *Links, load Load, wg *sync.WaitGroup) { //Notify main that this routine is done. defer wg.Done() //Time should be accepted both in epoch milliseconds or seconds. diff --git a/utils.go b/utils.go new file mode 100644 index 0000000000000000000000000000000000000000..cd45c4ee1d6886629bec4fc3f24562a0e5904082 --- /dev/null +++ b/utils.go @@ -0,0 +1,45 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + "os" +) + +//Prints the results for a given structure containing already processed Links. +func PrintResult(result *Links) { + for k, v := range result.URL { + log.Println("For URL: ", k) + for m, x := range v.Date { + log.Println("↳", m, "there are:", x.Counter, "unique visits.") + } + } +} + +//Function for returning response to the user if the code runs in container +//as an API +func WriteResult(w http.ResponseWriter, result *Links) { + for k, v := range result.URL { + fmt.Fprintf(w, "For URL, %v\n", k) + log.Println("For URL: ", k) + for m, x := range v.Date { + fmt.Fprintf(w, "↳%v there are: %v unique visits.\n", m, x.Counter) + log.Println("↳", m, "there are:", x.Counter, "unique visits.") + } + } +} + +//Extracts JSON payload and calls Process to handle the data. +func ExctractJSON(file *os.File) (load []Load) { + d := json.NewDecoder(file) + + var loads []Load + + err := d.Decode(&loads) + if err != nil { + log.Panic(err) + } + return loads +}