diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ad8410d --- /dev/null +++ b/.gitignore @@ -0,0 +1,39 @@ +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +.idea +tmp/ +*.tmp +scratch/ +build/ +*.swp +profile.cov +gin-bin + +# we don't vendor godep _workspace +**/Godeps/_workspace/** + +# OSX stuff +.DS_Store + +# Default name of executable +simple-rest-server/simple-rest-server diff --git a/simple-rest-server/delete.go b/simple-rest-server/delete.go new file mode 100644 index 0000000..91796c1 --- /dev/null +++ b/simple-rest-server/delete.go @@ -0,0 +1,69 @@ +package main + +import ( + "encoding/json" + "fmt" + "github.com/julienschmidt/httprouter" + "io/ioutil" + "log" + "net/http" +) + +type Remove struct { + ID int `json:"id"` +} + +// Delete API call +func DeleteTask(respWriter http.ResponseWriter, request *http.Request, _ httprouter.Params) { + var remove Remove + body, err := ioutil.ReadAll(request.Body) + if err != nil { + fmt.Fprintln(respWriter, "Bad request: %v", err) + log.Println(err) + respWriter.WriteHeader(400) + } + err = json.Unmarshal(body, &remove) + if err != nil { + fmt.Fprintln(respWriter, "Bad request: %v", err) + log.Println(err) + respWriter.WriteHeader(400) + } + + index, err := removeTaskByID(remove.ID) + if index == -1 { + fmt.Fprintln(respWriter, "Task ID not found. Unable to delete") + } else { + fmt.Fprintln(respWriter, "Task ID: "+string(index)+" found.") + } + if err != nil { + fmt.Fprintln(respWriter, "Bad request: %v", err) + log.Println(err) + respWriter.WriteHeader(400) + } +} + +func removeTaskByID(taskID int) (int, error) { + index, err := getIndexByTaskID(taskID) + if err != nil { + log.Println(err) + return index, err + } + + if index == -1 { + return index, err + } else if index == 0 { + accessTasks.Lock() + allTasks = allTasks[1:] + accessTasks.Unlock() + } else if index == len(allTasks)-1 { + accessTasks.Lock() + allTasks = allTasks[:len(allTasks)-1] + accessTasks.Unlock() + } else { + accessTasks.Lock() + allTasks = append(allTasks[:index-1], allTasks[index+1:]...) + accessTasks.Unlock() + } + accessTasks.Unlock() + return index, err +} diff --git a/simple-rest-server/get.go b/simple-rest-server/get.go new file mode 100644 index 0000000..63866c7 --- /dev/null +++ b/simple-rest-server/get.go @@ -0,0 +1,121 @@ +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "net/http" + "strconv" + "strings" + "time" + + "github.com/julienschmidt/httprouter" +) + +type Search struct { + Word string `json:"search"` + TimeBefore time.Time `json:"before"` +} + +func htmlParagraph(text *string, paragraph string) { + *text += "\n

" + paragraph + "

" +} + +func Index(respWriter http.ResponseWriter, request *http.Request, params httprouter.Params) { + htmlOut := `Todo List API.` + htmlParagraph(&htmlOut, "/add") + htmlParagraph(&htmlOut, "/delete") + htmlParagraph(&htmlOut, "/search") + htmlParagraph(&htmlOut, "/list") + htmlParagraph(&htmlOut, "/checkoff") + + respWriter.Header().Set("Content-Type", "text/html; charset=utf-8") + respWriter.Write([]byte(htmlOut)) +} + +func ListTask(respWriter http.ResponseWriter, request *http.Request, params httprouter.Params) { + output, _ := json.MarshalIndent(allTasks, "", " ") + fmt.Fprintln(respWriter, string(output)) +} + +func GetTask(respWriter http.ResponseWriter, request *http.Request, params httprouter.Params) { + i, err := strconv.Atoi(params.ByName("id")) + if err != nil { + fmt.Fprintf(respWriter, "Bad request: %v", err) + respWriter.WriteHeader(400) + return + } + i, err = getIndexByTaskID(i) + if err != nil { + fmt.Fprintf(respWriter, "Bad request: %v", err) + respWriter.WriteHeader(400) + return + } + output, _ := json.MarshalIndent(allTasks[i], "", " ") + fmt.Fprintln(respWriter, string(output)) +} + +func SearchTask(respWriter http.ResponseWriter, request *http.Request, params httprouter.Params) { + var query Search + body, err := ioutil.ReadAll(request.Body) + if err != nil { + fmt.Println(err) + fmt.Fprintln(respWriter, "Bad request: %v", err) + log.Println(err) + respWriter.WriteHeader(400) + } + err = json.Unmarshal(body, &query) + if err != nil { + fmt.Fprintln(respWriter, "Bad request: %v", err) + log.Println(err) + respWriter.WriteHeader(400) + } + + var indicies []int + fmt.Fprintln(respWriter, "Searching for "+query.Word) + timezero := time.Time{} + if query.Word != "" { + indicies = append(indicies, searchByWord(query.Word)[:]...) + } else if query.TimeBefore != timezero { + indicies = append(indicies, searchByTime(query.TimeBefore)[:]...) + } else { + fmt.Println("Undefined search query") + } + + if len(indicies) == 0 { + fmt.Fprintln(respWriter, "No Results!!!!!") + } + for _, index := range indicies { + accessTasks.Lock() + prettyJson, err := json.MarshalIndent(allTasks[index], "", " ") + accessTasks.Unlock() + if err != nil { + log.Print(err) + return + } + fmt.Fprintln(respWriter, string(prettyJson)) + } +} + +func searchByWord(query string) (result []int) { + accessTasks.Lock() + for i, task := range allTasks { + if strings.Contains(task.Task, query) { + result = append(result, i) + } + } + accessTasks.Unlock() + return +} + +func searchByTime(query time.Time) (result []int) { + accessTasks.Lock() + for i, task := range allTasks { + if task.TimeAdded.Before(query) || task.TimeAdded == query { + result = append(result, i) + } + } + accessTasks.Unlock() + return +} diff --git a/simple-rest-server/main.go b/simple-rest-server/main.go index 3394dde..996b815 100644 --- a/simple-rest-server/main.go +++ b/simple-rest-server/main.go @@ -1,30 +1,123 @@ package main import ( + "encoding/csv" + "errors" "fmt" + "io/ioutil" "log" "net/http" + "os" + "sync" + "time" "github.com/julienschmidt/httprouter" ) -func Index(respWriter http.ResponseWriter, request *http.Request, _ httprouter.Params) { - fmt.Fprintln(respWriter, "ToDo RESTful App") +type Task struct { + ID int + Checked bool `json:"checked"` + TimeAdded time.Time `json:"time_added"` + Deadline time.Time `json:"deadline"` + Task string `json:"task"` } -func TodoIndex(respWriter http.ResponseWriter, request *http.Request, _ httprouter.Params) { - fmt.Fprintln(respWriter, "Return ToDo List") -} +var ( + allTasks []Task + accessTasks = &sync.Mutex{} +) -func TodoShow(respWriter http.ResponseWriter, request *http.Request, params httprouter.Params) { - fmt.Fprintf(respWriter, "Show Details for ToDo '%s'\n", params.ByName("todoId")) -} +const ( + timeFormat = time.RFC3339 +) func main() { + + // Loading the csv file into the RAM + csvfile, err := os.Open("tasks.csv") + ifPanic(err) + + // Loading csv with csv Library returns [][]string + rawCSVdata, err := csv.NewReader(csvfile).ReadAll() + ifPanic(err) + + // For each line in csv do.... + for i, each := range rawCSVdata { + timeAdded, err := time.Parse(timeFormat, each[2]) + ifPanic(err) + deadline, err := time.Parse(timeFormat, each[3]) + ifPanic(err) + status := false + if each[1] == "true" { + status = true + } + allTasks = append(allTasks, Task{ID: i, Task: each[4], TimeAdded: timeAdded, Deadline: deadline, Checked: status}) + } + + // Autosave every minutes + csvfile.Close() + + // go func() starts a new process. + go func() { + for { + saveCSV() + time.Sleep(5 * time.Second) + } + }() + + // Start the API router := httprouter.New() - router.GET("/", Index) - router.GET("/todos", TodoIndex) - router.GET("/todos/:todoId", TodoShow) + + router.GET("/v1/task", ListTask) + router.POST("/v1/task", AddTask) + //router.PUT("/v1/task", nil) + //router.DELETE("/v1/task", nil) + + router.GET("/v1/task/:id", GetTask) + //router.POST("/v1/task/:id", nil) + router.PUT("/v1/task/:id", Modify) + router.DELETE("/v1/task/:id", DeleteTask) + + router.GET("/v1/query", SearchTask) log.Fatal(http.ListenAndServe(":8080", router)) } + +// Function to save the CSV +func saveCSV() error { + myString := "" + accessTasks.Lock() + for _, each := range allTasks { + myString += fmt.Sprintf("%v,%v,%v,%v,\"%v\"\n", each.ID, each.Checked, each.TimeAdded.Format(timeFormat), each.Deadline.Format(timeFormat), each.Task) + } + err := ioutil.WriteFile("tasks.csv", []byte(myString), 0644) + accessTasks.Unlock() + return err +} + +func ifPanic(err error) { + if err != nil { + // Maybe change to log later + fmt.Println(err) + os.Exit(1) + } +} + +func getIndexByTaskID(taskID int) (index int, err error) { + // If we didn't find the task ID - return an error and set the index to -1... + err = errors.New("GetIndexByTaskID: TaskID " + string(taskID) + " Not Found.") + index = -1 + + accessTasks.Lock() + + // Search for the taskID and return the index. + for i, line := range allTasks { + if taskID == line.ID { + index = i + err = nil + break + } + } + accessTasks.Unlock() + return +} diff --git a/simple-rest-server/post.go b/simple-rest-server/post.go new file mode 100644 index 0000000..0e59bac --- /dev/null +++ b/simple-rest-server/post.go @@ -0,0 +1,47 @@ +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + + "github.com/julienschmidt/httprouter" +) + +func AddTask(respWriter http.ResponseWriter, request *http.Request, _ httprouter.Params) { + var newTask Task + body, err := ioutil.ReadAll(request.Body) + if err != nil { + fmt.Println(err) + } + err = json.Unmarshal(body, &newTask) + if err != nil { + fmt.Fprintf(respWriter, "Bad request: %v", err) + respWriter.WriteHeader(400) + return + } + + accessTasks.Lock() + newTask.ID = 0 + if len(allTasks) > 0 { + newTask.ID = allTasks[len(allTasks)-1].ID + 1 + } + // lastID := allTasks[len(allTasks)-1].ID + // for i := range newTask { + // lastID++ + // newTask[i].ID = lastID + 1 + // } + + // allTasks = append(allTasks, newTask...) + newTask.ID = allTasks[len(allTasks)-1].ID + 1 + allTasks = append(allTasks, newTask) + accessTasks.Unlock() + + err = saveCSV() + if err != nil { + fmt.Fprintf(respWriter, "Internal error: %v", err) + respWriter.WriteHeader(500) + return + } +} diff --git a/simple-rest-server/put.go b/simple-rest-server/put.go new file mode 100644 index 0000000..19ab1bf --- /dev/null +++ b/simple-rest-server/put.go @@ -0,0 +1,56 @@ +package main + +import ( + "encoding/json" + "io/ioutil" + "log" + "net/http" + "strconv" + + "github.com/julienschmidt/httprouter" +) + +type Check struct { + ID int `json:"id"` +} + +func Modify(respWriter http.ResponseWriter, request *http.Request, ps httprouter.Params) { + var check Check + body, err := ioutil.ReadAll(request.Body) + if err != nil { + log.Println(err) + return + } + err = json.Unmarshal(body, &check) + if err != nil { + log.Println(err) + return + } + ID, err := strconv.Atoi(ps.ByName("id")) + if err != nil { + // TODO: display 400, id is not a number + return + } + + // TODO: be able to change anything + checkTaskAsComplete(ID) + // TODO: error 400, print it on the body +} + +func checkTaskAsComplete(taskID int) (err error) { + // Get the index. + index, err := getIndexByTaskID(taskID) + if err != nil { + log.Println(err) + return + } + + // Lock the tasks. + accessTasks.Lock() + + // Set the task to checked. + allTasks[index].Checked = true + + accessTasks.Unlock() + return +} diff --git a/simple-rest-server/tasks.csv b/simple-rest-server/tasks.csv new file mode 100644 index 0000000..d9fc8fe --- /dev/null +++ b/simple-rest-server/tasks.csv @@ -0,0 +1,3 @@ +0,false,2012-04-23T18:25:43Z,2012-04-23T18:25:43Z,"plop" +1,true,2012-04-23T18:25:43Z,2012-04-23T18:25:43Z,"As a user I wanna do something" +2,true,2012-04-23T18:25:43Z,2012-04-23T18:25:43Z,"As a user I wanna do something"