diff --git a/examples/to-do-app/README.md b/examples/to-do-app/README.md new file mode 100644 index 0000000..2be6067 --- /dev/null +++ b/examples/to-do-app/README.md @@ -0,0 +1,15 @@ +TODO App for BaliGophers +============= +Sample of TODO app simple CRUD as REST API + +How to use: +-------- + +1. Run `go build` +2. Import the postman collection and execute the endpoint. No Auth needed. + +TODO +--- + +1. How to handle `error` in cleaner way +2. Add log \ No newline at end of file diff --git a/examples/to-do-app/db/connector/connector.go b/examples/to-do-app/db/connector/connector.go new file mode 100644 index 0000000..4a0e7c0 --- /dev/null +++ b/examples/to-do-app/db/connector/connector.go @@ -0,0 +1,18 @@ +package connector + +import ( + "github.com/jinzhu/gorm" + _ "github.com/jinzhu/gorm/dialects/mysql" +) + +func Connect() (*gorm.DB, error) { + db, err := gorm.Open("mysql", "root@/go-todo-app?charset=utf8&parseTime=True&loc=Local") + + if err != nil { + return nil, err + } + + db.SingularTable(true) + + return db, err +} diff --git a/examples/to-do-app/db/migration/migration.go b/examples/to-do-app/db/migration/migration.go new file mode 100644 index 0000000..473f4aa --- /dev/null +++ b/examples/to-do-app/db/migration/migration.go @@ -0,0 +1,19 @@ +package migration + +import ( + "github.com/gusdecool/backpack/examples/to-do-app/db/connector" + "github.com/gusdecool/backpack/examples/to-do-app/db/model" +) + +func Migrate() error { + db, err := connector.Connect() + + if err != nil { + return err + } + + defer db.Close() + db.AutoMigrate(&model.Task{}) + + return nil +} \ No newline at end of file diff --git a/examples/to-do-app/db/model/task.go b/examples/to-do-app/db/model/task.go new file mode 100644 index 0000000..656c68a --- /dev/null +++ b/examples/to-do-app/db/model/task.go @@ -0,0 +1,11 @@ +package model + +import ( + "time" +) + +type Task struct { + Id int `gorm:"AUTO_INCREMENT"` + Name string `gorm:"type:text;not null"` + CreatedAt time.Time `gorm:"type:timestamp;not null"` +} diff --git a/examples/to-do-app/db/repository/task/task.go b/examples/to-do-app/db/repository/task/task.go new file mode 100644 index 0000000..8b7d6a5 --- /dev/null +++ b/examples/to-do-app/db/repository/task/task.go @@ -0,0 +1,86 @@ +package task + +import ( + "errors" + "fmt" + "github.com/gusdecool/backpack/examples/to-do-app/db/connector" + "github.com/gusdecool/backpack/examples/to-do-app/db/model" +) + +func FindAll() ([]model.Task, error) { + db, err := connector.Connect() + var tasks []model.Task + + if err != nil { + return tasks, err + } + + defer db.Close() + db.Find(&tasks) + + return tasks, nil +} + +func GetOneById(id int) (model.Task, error) { + db, err := connector.Connect() + var task model.Task + + if err != nil { + return task, err + } + + defer db.Close() + + if db.First(&task, id).RowsAffected == 0 { + return task, errors.New(fmt.Sprintf("can't find task with id %d", id)) + } + + return task, nil +} + +func Create(task *model.Task) (*model.Task, error) { + db, err := connector.Connect() + + if err != nil { + return task, err + } + + defer db.Close() + + if db.NewRecord(task) == false { + return task, errors.New("primary key not blank") + } + + db.Create(&task) + + return task, nil +} + +func Update(task *model.Task) (*model.Task, error) { + db, err := connector.Connect() + + if err != nil { + return task, err + } + + defer db.Close() + err = db.Save(task).Error + + if err != nil { + return task, err + } + + return task, nil +} + +func Delete(task *model.Task) error { + db, err := connector.Connect() + + if err != nil { + return err + } + + defer db.Close() + + return db.Delete(task).Error +} \ No newline at end of file diff --git a/examples/to-do-app/go-todo-app.postman_collection.json b/examples/to-do-app/go-todo-app.postman_collection.json new file mode 100644 index 0000000..457feae --- /dev/null +++ b/examples/to-do-app/go-todo-app.postman_collection.json @@ -0,0 +1,133 @@ +{ + "info": { + "_postman_id": "e9a72a8d-ec0d-4863-82e5-ce0cc571940f", + "name": "go-todo-app", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "/task", + "request": { + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "http://127.0.0.1:8080/task", + "protocol": "http", + "host": [ + "127", + "0", + "0", + "1" + ], + "port": "8080", + "path": [ + "task" + ] + } + }, + "response": [] + }, + { + "name": "/task", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"name\": \"foo again\"\n}" + }, + "url": { + "raw": "http://127.0.0.1:8080/task", + "protocol": "http", + "host": [ + "127", + "0", + "0", + "1" + ], + "port": "8080", + "path": [ + "task" + ] + } + }, + "response": [] + }, + { + "name": "/task/{id}", + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"name\": \"foo-1\"\n}" + }, + "url": { + "raw": "http://127.0.0.1:8080/task/2", + "protocol": "http", + "host": [ + "127", + "0", + "0", + "1" + ], + "port": "8080", + "path": [ + "task", + "2" + ] + } + }, + "response": [] + }, + { + "name": "/task/{id}", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "http://127.0.0.1:8080/task/1", + "protocol": "http", + "host": [ + "127", + "0", + "0", + "1" + ], + "port": "8080", + "path": [ + "task", + "1" + ] + } + }, + "response": [] + } + ] +} \ No newline at end of file diff --git a/examples/to-do-app/http/controller/task/task.go b/examples/to-do-app/http/controller/task/task.go new file mode 100644 index 0000000..172db8e --- /dev/null +++ b/examples/to-do-app/http/controller/task/task.go @@ -0,0 +1,130 @@ +package task + +import ( + "encoding/json" + "github.com/gorilla/mux" + "github.com/gusdecool/backpack/examples/to-do-app/db/model" + repo "github.com/gusdecool/backpack/examples/to-do-app/db/repository/task" + "github.com/gusdecool/backpack/examples/to-do-app/http/utility" + "net/http" + "strconv" + "time" +) + +func List(response http.ResponseWriter, request *http.Request) { + tasks, err := repo.FindAll() + + if err != nil { + utility.HandleErrorResponse(err, response) + return + } + + tasksByte, err := json.Marshal(tasks) + + if err != nil { + utility.HandleErrorResponse(err, response) + return + } + + utility.HandleSuccessResponse(tasksByte, response) +} + +func Create(response http.ResponseWriter, request *http.Request) { + taskModel, err := decode(request) + + if err != nil { + utility.HandleErrorResponse(err, response) + return + } + + taskModel.CreatedAt = time.Now() + _, err = repo.Create(&taskModel) + + if err != nil { + utility.HandleErrorResponse(err, response) + return + } + + taskByte, err := json.Marshal(taskModel) + + if err != nil { + utility.HandleErrorResponse(err, response) + return + } + + utility.HandleSuccessResponse(taskByte, response) +} + +func Update(response http.ResponseWriter, request *http.Request) { + parameters := mux.Vars(request) + taskId, err := strconv.Atoi(parameters["id"]) + + if err != nil { + utility.HandleErrorResponse(err, response) + return + } + + taskModel, err := repo.GetOneById(taskId) + + if err != nil { + utility.HandleErrorResponse(err, response) + return + } + + requestUpdateModel, err := decode(request) + + if err != nil { + utility.HandleErrorResponse(err, response) + return + } + + taskModel.Name = requestUpdateModel.Name + _, err = repo.Update(&taskModel) + + if err != nil { + utility.HandleErrorResponse(err, response) + return + } + + taskByte, err := json.Marshal(taskModel) + + if err != nil { + utility.HandleErrorResponse(err, response) + return + } + + utility.HandleSuccessResponse(taskByte, response) +} + +func Delete(response http.ResponseWriter, request *http.Request) { + parameters := mux.Vars(request) + taskId, err := strconv.Atoi(parameters["id"]) + + if err != nil { + utility.HandleErrorResponse(err, response) + return + } + + taskModel, err := repo.GetOneById(taskId) + + if err != nil { + utility.HandleErrorResponse(err, response) + return + } + + err = repo.Delete(&taskModel) + + if err != nil { + utility.HandleErrorResponse(err, response) + return + } + + utility.HandleSuccessEmptyResponse(response) +} + +func decode(request *http.Request) (model.Task, error) { + var taskModel model.Task + err := json.NewDecoder(request.Body).Decode(&taskModel) + + return taskModel, err +} \ No newline at end of file diff --git a/examples/to-do-app/http/router/router.go b/examples/to-do-app/http/router/router.go new file mode 100644 index 0000000..dd31657 --- /dev/null +++ b/examples/to-do-app/http/router/router.go @@ -0,0 +1,22 @@ +package router + +import ( + "github.com/gorilla/mux" + "github.com/gusdecool/backpack/examples/to-do-app/http/controller/task" + "log" + "net/http" +) + +func Register() { + router := mux.NewRouter() + + registerTask(router) + log.Fatal(http.ListenAndServe("127.0.0.1:8080", router)) +} + +func registerTask(router *mux.Router) { + router.HandleFunc("/task", task.List).Methods("GET") + router.HandleFunc("/task", task.Create).Methods("POST") + router.HandleFunc("/task/{id}", task.Update).Methods("PUT") + router.HandleFunc("/task/{id}", task.Delete).Methods("DELETE") +} diff --git a/examples/to-do-app/http/utility/utility.go b/examples/to-do-app/http/utility/utility.go new file mode 100644 index 0000000..6f0595d --- /dev/null +++ b/examples/to-do-app/http/utility/utility.go @@ -0,0 +1,19 @@ +package utility + +import "net/http" + +func HandleSuccessResponse(message []byte, response http.ResponseWriter) { + response.Header().Set("Content-Type", "application/json") + response.WriteHeader(http.StatusOK) + response.Write(message) +} + +func HandleSuccessEmptyResponse(response http.ResponseWriter) { + response.Header().Set("Content-Type", "application/json") + response.WriteHeader(http.StatusNoContent) +} + +func HandleErrorResponse(err error, response http.ResponseWriter) { + response.WriteHeader(http.StatusBadRequest) + response.Write([]byte(err.Error())) +} diff --git a/examples/to-do-app/main.go b/examples/to-do-app/main.go new file mode 100644 index 0000000..41717c1 --- /dev/null +++ b/examples/to-do-app/main.go @@ -0,0 +1,25 @@ +package main + +import ( + "flag" + "fmt" + "github.com/gusdecool/backpack/examples/to-do-app/db/migration" + "github.com/gusdecool/backpack/examples/to-do-app/http/router" + +) + +func main() { + runMigration := flag.Bool("run-migration", false, "run migration? default: false") + flag.Parse() + + if *runMigration == true { + err := migration.Migrate() + + if err != nil { + fmt.Println(err) + return + } + } + + router.Register() +} \ No newline at end of file