From 8c21fb34a8b779ca17cd043d89c221cfc50ea5aa Mon Sep 17 00:00:00 2001 From: Quang Ha Date: Tue, 21 Jun 2022 20:07:09 +0700 Subject: [PATCH 01/41] structure project --- .gitignore | 3 ++- README.md | 30 ------------------------------ go.mod | 12 ++++++++++++ go.sum | 13 +++++++++++++ main.go | 5 +++++ models/models.go | 46 ++++++++++++++++++++++++++++++++++++++++++++++ utils/utils.go | 15 +++++++++++++++ 7 files changed, 93 insertions(+), 31 deletions(-) create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 models/models.go create mode 100644 utils/utils.go diff --git a/.gitignore b/.gitignore index a512c8b38..c584ca2a3 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,5 @@ out/ -.idea \ No newline at end of file +.idea +.env \ No newline at end of file diff --git a/README.md b/README.md index 8df9d4d3a..e69de29bb 100644 --- a/README.md +++ b/README.md @@ -1,30 +0,0 @@ -### Requirements - -- Implement one single API which accepts a todo task and records it - - There is a maximum **limit of N tasks per user** that can be added **per day**. - - Different users can have **different** maximum daily limit. -- Write integration (functional) tests -- Write unit tests -- Choose a suitable architecture to make your code simple, organizable, and maintainable -- Write a concise README - - How to run your code locally? - - A sample “curl” command to call your API - - How to run your unit tests locally? - - What do you love about your solution? - - What else do you want us to know about however you do not have enough time to complete? - -### Notes - -- We're using Golang at Manabie. **However**, we encourage you to use the programming language that you are most comfortable with because we want you to **shine** with all your skills and knowledge. - -### How to submit your solution? - -- Fork this repo and show us your development progress via a PR - -### Interesting facts about Manabie - -- Monthly there are about 2 million lines of code changes (inserted/updated/deleted) committed into our GitHub repositories. To avoid **regression bugs**, we write different kinds of **automated tests** (unit/integration (functionality)/end2end) as parts of the definition of done of our assigned tasks. -- We nurture the cultural values: **knowledge sharing** and **good communication**, therefore good written documents and readable, organizable, and maintainable code are in our blood when we build any features to grow our products. -- We have **collaborative** culture at Manabie. Feel free to ask trieu@manabie.com any questions. We are very happy to answer all of them. - -Thank you for spending time to read and attempt our take-home assessment. We are looking forward to your submission. diff --git a/go.mod b/go.mod new file mode 100644 index 000000000..baeffc715 --- /dev/null +++ b/go.mod @@ -0,0 +1,12 @@ +module github.com/manabie-com/togo + +go 1.18 + +require ( + github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect + github.com/gorilla/mux v1.8.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/joho/godotenv v1.4.0 // indirect + gorm.io/gorm v1.23.6 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 000000000..22906c592 --- /dev/null +++ b/go.sum @@ -0,0 +1,13 @@ +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= +github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +gorm.io/gorm v1.23.6 h1:KFLdNgri4ExFFGTRGGFWON2P1ZN28+9SJRN8voOoYe0= +gorm.io/gorm v1.23.6/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= diff --git a/main.go b/main.go new file mode 100644 index 000000000..790580777 --- /dev/null +++ b/main.go @@ -0,0 +1,5 @@ +package main + +func main() { + +} diff --git a/models/models.go b/models/models.go new file mode 100644 index 000000000..2318fe87d --- /dev/null +++ b/models/models.go @@ -0,0 +1,46 @@ +package models + +import "time" + +type User struct { + ID int32 `json:"id"` + Name string `json:"name"` + Password string `json:"password"` + IsPayment bool `json:"isPayment"` + LimitTasks int `json:"limitTasks"` + Tasks *[]Task `json:"tasks"` +} + +type Task struct { + ID int32 `json:"id"` + Name string `json:"name"` + Content string `json:"content"` + Status Status `json:"status"` + CreatedAt time.Time `json:"createdAt"` + UserId int32 `json:"userId"` + ChildrenTask *[]ChildrenTask `json:"childrenTask"` +} + +type ChildrenTask struct { + ID int32 `json:"id"` + Name string `json:"name"` + Content string `json:"content"` + Status Status `json:"status"` + CreatedAt time.Time `json:"createdAt"` + TaskID int32 `json:"taskId"` +} +type Status string + +const ( + toDo Status = "todo" + doing Status = "doing" + done Status = "done" +) + +func (e Status) IsValid() bool { + switch e { + case toDo, doing, done: + return true + } + return false +} diff --git a/utils/utils.go b/utils/utils.go new file mode 100644 index 000000000..5f5106558 --- /dev/null +++ b/utils/utils.go @@ -0,0 +1,15 @@ +package utils + +import ( + "encoding/json" + "net/http" +) + +func Message(status bool, message string) map[string]interface{} { + return map[string]interface{}{"status": status, "message": message} +} + +func Respond(w http.ResponseWriter, data map[string]interface{}) { + w.Header().Add("Content-Type", "application/json") + json.NewEncoder(w).Encode(data) +} From e6ce86300c4708d6ca404c4eeebc93afa54ad7ae Mon Sep 17 00:00:00 2001 From: Quang Ha Date: Wed, 22 Jun 2022 00:10:32 +0700 Subject: [PATCH 02/41] create few task --- controllers/task.go | 0 controllers/user.go | 0 go.mod | 7 +++++++ go.sum | 44 +++++++++++++++++++++++++++++++++++++++++++ main.go | 4 +++- models/models.go | 46 --------------------------------------------- models/task.go | 43 ++++++++++++++++++++++++++++++++++++++++++ models/user.go | 36 +++++++++++++++++++++++++++++++++++ 8 files changed, 133 insertions(+), 47 deletions(-) create mode 100644 controllers/task.go create mode 100644 controllers/user.go delete mode 100644 models/models.go create mode 100644 models/task.go create mode 100644 models/user.go diff --git a/controllers/task.go b/controllers/task.go new file mode 100644 index 000000000..e69de29bb diff --git a/controllers/user.go b/controllers/user.go new file mode 100644 index 000000000..e69de29bb diff --git a/go.mod b/go.mod index baeffc715..c35618cf1 100644 --- a/go.mod +++ b/go.mod @@ -4,9 +4,16 @@ go 1.18 require ( github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect + github.com/go-playground/locales v0.14.0 // indirect + github.com/go-playground/universal-translator v0.18.0 // indirect + github.com/go-playground/validator/v10 v10.11.0 // indirect github.com/gorilla/mux v1.8.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/joho/godotenv v1.4.0 // indirect + github.com/leodido/go-urn v1.2.1 // indirect + golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect + golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 // indirect + golang.org/x/text v0.3.7 // indirect gorm.io/gorm v1.23.6 // indirect ) diff --git a/go.sum b/go.sum index 22906c592..a1b57c703 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,15 @@ +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw= +github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= @@ -9,5 +19,39 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 h1:siQdpVirKtzPhKl3lZWozZraCFObP8S1v6PRp0bLrtU= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/gorm v1.23.6 h1:KFLdNgri4ExFFGTRGGFWON2P1ZN28+9SJRN8voOoYe0= gorm.io/gorm v1.23.6/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= diff --git a/main.go b/main.go index 790580777..2fa4a6917 100644 --- a/main.go +++ b/main.go @@ -1,5 +1,7 @@ package main func main() { - + // router := mux.NewRouter() + // router.HandleFunc() + // ... } diff --git a/models/models.go b/models/models.go deleted file mode 100644 index 2318fe87d..000000000 --- a/models/models.go +++ /dev/null @@ -1,46 +0,0 @@ -package models - -import "time" - -type User struct { - ID int32 `json:"id"` - Name string `json:"name"` - Password string `json:"password"` - IsPayment bool `json:"isPayment"` - LimitTasks int `json:"limitTasks"` - Tasks *[]Task `json:"tasks"` -} - -type Task struct { - ID int32 `json:"id"` - Name string `json:"name"` - Content string `json:"content"` - Status Status `json:"status"` - CreatedAt time.Time `json:"createdAt"` - UserId int32 `json:"userId"` - ChildrenTask *[]ChildrenTask `json:"childrenTask"` -} - -type ChildrenTask struct { - ID int32 `json:"id"` - Name string `json:"name"` - Content string `json:"content"` - Status Status `json:"status"` - CreatedAt time.Time `json:"createdAt"` - TaskID int32 `json:"taskId"` -} -type Status string - -const ( - toDo Status = "todo" - doing Status = "doing" - done Status = "done" -) - -func (e Status) IsValid() bool { - switch e { - case toDo, doing, done: - return true - } - return false -} diff --git a/models/task.go b/models/task.go new file mode 100644 index 000000000..7eb4392aa --- /dev/null +++ b/models/task.go @@ -0,0 +1,43 @@ +package models + +import "time" + +type Task struct { + ID int32 `json:"id"` + Name string `json:"name"` + Content string `json:"content"` + Status Status `json:"status"` + CreatedAt time.Time `json:"createdAt"` + UserId int32 `json:"userId"` +} + +type Status string + +const ( + toDo Status = "todo" + doing Status = "doing" + done Status = "done" +) + +func (e Status) isValidStatus() bool { + switch e { + case toDo, doing, done: + return true + } + return false +} + +type task interface { + /* map[string]interface{} like + { + status: "success"||"failure", + message: Optional + data: { + data + } + } + */ + Create() map[string]interface{} + GetTasks(userId int32) map[string]interface{} + GetTask(taskId uint32) map[string]interface{} +} diff --git a/models/user.go b/models/user.go new file mode 100644 index 000000000..8881e9819 --- /dev/null +++ b/models/user.go @@ -0,0 +1,36 @@ +package models + +type User struct { + ID uint32 `json:"id"` + Name string `json:"name" validate:"required"` + Password string `json:"password" validate:"required"` + IsPayment bool `json:"isPayment" default:"false"` + LimitDayTasks uint `json:"limitDayTasks" default:"10"` + Tasks *[]Task `json:"tasks"` +} + +func (u *User) validate() error { + // v := validator.New() + // err := v.Struct(u) + // for _, e := range err.(validator.ValidationErrors) { + // fmt.Print(e) + // } + return nil +} + +type user interface { + /* map[string]interface{} return type like + { + status: "success"||"failure", + message: Optional + data: { + data + } + } + */ + Create(map[string]interface{}) map[string]interface{} + // update to premium user => increase user daily tasks + Update(map[string]interface{}) map[string]interface{} + + GetUser(id string) map[string]interface{} +} From d1125ad6df2a65a6c0466740ae2d1e98d78bcdcf Mon Sep 17 00:00:00 2001 From: Quang Ha Date: Wed, 22 Jun 2022 11:56:59 +0700 Subject: [PATCH 03/41] update fake code --- controllers/task.go | 36 ++++++++++++++++++++++++++++++++++++ controllers/user.go | 38 ++++++++++++++++++++++++++++++++++++++ main.go | 36 +++++++++++++++++++++++++++++++++--- models/task.go | 17 +---------------- models/user.go | 22 +++------------------- utils/utils.go | 9 +++------ 6 files changed, 114 insertions(+), 44 deletions(-) diff --git a/controllers/task.go b/controllers/task.go index e69de29bb..8bec52481 100644 --- a/controllers/task.go +++ b/controllers/task.go @@ -0,0 +1,36 @@ +package controllers + +import ( + "net/http" + + u "github.com/manabie-com/togo/utils" +) + +var GetTasks = func(w http.ResponseWriter, r *http.Request) { + u.Respond(w, http.StatusOK, map[string]interface{}{}) +} + +var GetTask = func(w http.ResponseWriter, r *http.Request) { + // check taskID exist + // if not exists + u.Respond(w, http.StatusNotFound, map[string]interface{}{}) + // else + u.Respond(w, http.StatusOK, map[string]interface{}{}) +} + +var Add = func(w http.ResponseWriter, r *http.Request) { + // check task number today greater than or equal to current user limitDayTasks + // if true + u.Respond(w, http.StatusNotAcceptable, map[string]interface{}{}) + // else + // update task number today += 1 + u.Respond(w, http.StatusCreated, map[string]interface{}{}) +} + +var Edit = func(w http.ResponseWriter, r *http.Request) { + // check taskID exist + // if not exists + u.Respond(w, http.StatusNotFound, map[string]interface{}{}) + // else + u.Respond(w, http.StatusOK, map[string]interface{}{}) +} diff --git a/controllers/user.go b/controllers/user.go index e69de29bb..127df163c 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -0,0 +1,38 @@ +package controllers + +import ( + "net/http" + + u "github.com/manabie-com/togo/utils" +) + +var SignUp = func(w http.ResponseWriter, r *http.Request) { + + // send token jwt + // ... + u.Respond(w, http.StatusCreated, map[string]interface{}{}) + +} + +var Login = func(w http.ResponseWriter, r *http.Request) { + // get email password + // if email and password not exist + u.Respond(w, http.StatusBadRequest, map[string]interface{}{}) + // if email exist and password incorrect + u.Respond(w, http.StatusUnauthorized, map[string]interface{}{}) + // if email and password OK + // send token to client + u.Respond(w, http.StatusOK, map[string]interface{}{}) +} + +var GetMe = func(w http.ResponseWriter, r *http.Request) { + // if jwt invalid + u.Respond(w, http.StatusBadRequest, map[string]interface{}{}) + // decode jwt -> get userID -> get user + u.Respond(w, http.StatusOK, map[string]interface{}{}) +} + +var UpdateMe = func(w http.ResponseWriter, r *http.Request) { + // success + u.Respond(w, http.StatusOK, map[string]interface{}{}) +} diff --git a/main.go b/main.go index 2fa4a6917..d00aae02d 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,37 @@ package main +import ( + "fmt" + "net/http" + "os" + + "github.com/gorilla/mux" + "github.com/manabie-com/togo/controllers" +) + func main() { - // router := mux.NewRouter() - // router.HandleFunc() - // ... + router := mux.NewRouter() + + // sub router like http://:/api/users + userRouter := router.PathPrefix("/api/users").Subrouter() + userRouter.HandleFunc("/", controllers.GetMe).Methods("GET") + userRouter.HandleFunc("/signUp", controllers.SignUp).Methods("POST") + userRouter.HandleFunc("/login", controllers.Login).Methods("POST") + userRouter.HandleFunc("/", controllers.UpdateMe).Methods("PATCH") + // sub router like http://:/api/tasks + taskRouter := router.PathPrefix("/api/tasks").Subrouter() + taskRouter.HandleFunc("/", controllers.GetTasks).Methods("GET") + taskRouter.HandleFunc("/{id}", controllers.GetTask).Methods("GET") + taskRouter.HandleFunc("/add", controllers.Add).Methods("POST") + taskRouter.HandleFunc("/{id}", controllers.Edit).Methods("PATCH") + + port := os.Getenv("PORT") + if port == "" { + port = "8000" + } + fmt.Println(port) + err := http.ListenAndServe(":"+port, router) + if err != nil { + fmt.Print(err) + } } diff --git a/models/task.go b/models/task.go index 7eb4392aa..bdce0a377 100644 --- a/models/task.go +++ b/models/task.go @@ -4,7 +4,7 @@ import "time" type Task struct { ID int32 `json:"id"` - Name string `json:"name"` + Name string `json:"name" validate:"required"` Content string `json:"content"` Status Status `json:"status"` CreatedAt time.Time `json:"createdAt"` @@ -26,18 +26,3 @@ func (e Status) isValidStatus() bool { } return false } - -type task interface { - /* map[string]interface{} like - { - status: "success"||"failure", - message: Optional - data: { - data - } - } - */ - Create() map[string]interface{} - GetTasks(userId int32) map[string]interface{} - GetTask(taskId uint32) map[string]interface{} -} diff --git a/models/user.go b/models/user.go index 8881e9819..ff51b3613 100644 --- a/models/user.go +++ b/models/user.go @@ -2,10 +2,11 @@ package models type User struct { ID uint32 `json:"id"` + Email string `json:"email" validate:"required"` Name string `json:"name" validate:"required"` Password string `json:"password" validate:"required"` - IsPayment bool `json:"isPayment" default:"false"` - LimitDayTasks uint `json:"limitDayTasks" default:"10"` + IsPayment bool `json:"isPayment"` + LimitDayTasks uint `json:"limitDayTasks"` Tasks *[]Task `json:"tasks"` } @@ -17,20 +18,3 @@ func (u *User) validate() error { // } return nil } - -type user interface { - /* map[string]interface{} return type like - { - status: "success"||"failure", - message: Optional - data: { - data - } - } - */ - Create(map[string]interface{}) map[string]interface{} - // update to premium user => increase user daily tasks - Update(map[string]interface{}) map[string]interface{} - - GetUser(id string) map[string]interface{} -} diff --git a/utils/utils.go b/utils/utils.go index 5f5106558..ebe98c1ab 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -5,11 +5,8 @@ import ( "net/http" ) -func Message(status bool, message string) map[string]interface{} { - return map[string]interface{}{"status": status, "message": message} -} - -func Respond(w http.ResponseWriter, data map[string]interface{}) { - w.Header().Add("Content-Type", "application/json") +func Respond(w http.ResponseWriter, status int, data map[string]interface{}) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(status) json.NewEncoder(w).Encode(data) } From 2150f2db05b86f9dce94693efe5feb67339c7cf1 Mon Sep 17 00:00:00 2001 From: Quang Ha Date: Wed, 22 Jun 2022 20:33:06 +0700 Subject: [PATCH 04/41] struture and implement few function --- app/app.go | 89 ++++++++++++++++++++++++++++++++ controllers/auth.go | 73 ++++++++++++++++++++++++++ controllers/task.go | 9 ++-- controllers/user.go | 32 +++++++++--- go.mod | 11 +++- go.sum | 121 ++++++++++++++++++++++++++++++++++++++++++++ main.go | 40 +++++---------- models/user.go | 28 +++++----- utils/utils.go | 9 ++-- 9 files changed, 353 insertions(+), 59 deletions(-) create mode 100644 app/app.go create mode 100644 controllers/auth.go diff --git a/app/app.go b/app/app.go new file mode 100644 index 000000000..60e3b2f52 --- /dev/null +++ b/app/app.go @@ -0,0 +1,89 @@ +package app + +import ( + "database/sql" + "fmt" + "log" + "net/http" + "os" + + "github.com/gorilla/mux" + "github.com/joho/godotenv" + c "github.com/manabie-com/togo/controllers" +) + +type App struct { + Router *mux.Router + DB *sql.DB +} + +func (a *App) Init() { + if err := godotenv.Load(); err != nil { + log.Fatal("Error loading .env file") + } + connectURL := os.Getenv("CONNECT_STR") + db, err := sql.Open("postgres", connectURL) + if err != nil { + log.Fatal(err) + } + defer db.Close() + + fmt.Println("Database is connected!") + a.DB = db + a.Router = mux.NewRouter() + a.Routes() +} + +func (a *App) Routes() { + + router := a.Router + // sub router like http://:/api/users + userRouter := router.PathPrefix("/api/users").Subrouter() + userRouter.HandleFunc("/", a.GetMe).Methods("GET") + userRouter.HandleFunc("/signup", a.SignUp).Methods("POST") + userRouter.HandleFunc("/login", a.Login).Methods("POST") + userRouter.HandleFunc("/", a.UpdateMe).Methods("PATCH") + // sub router like http://:/api/tasks + taskRouter := router.PathPrefix("/api/tasks").Subrouter() + taskRouter.HandleFunc("/", a.GetTasks).Methods("GET") + taskRouter.HandleFunc("/{id}", a.GetTask).Methods("GET") + taskRouter.HandleFunc("/add", a.Add).Methods("POST") + taskRouter.HandleFunc("/{id}", a.Edit).Methods("PATCH") + // runs database +} + +func (a *App) GetMe(w http.ResponseWriter, r *http.Request) { + c.GetMe(a.DB, w, r) +} + +func (a *App) SignUp(w http.ResponseWriter, r *http.Request) { + c.SignUp(a.DB, w, r) +} + +func (a *App) Login(w http.ResponseWriter, r *http.Request) { + c.Login(a.DB, w, r) +} + +func (a *App) UpdateMe(w http.ResponseWriter, r *http.Request) { + c.UpdateMe(a.DB, w, r) +} + +func (a *App) GetTasks(w http.ResponseWriter, r *http.Request) { + c.GetTasks(a.DB, w, r) +} + +func (a *App) GetTask(w http.ResponseWriter, r *http.Request) { + c.GetTask(a.DB, w, r) +} + +func (a *App) Add(w http.ResponseWriter, r *http.Request) { + c.Add(a.DB, w, r) +} + +func (a *App) Edit(w http.ResponseWriter, r *http.Request) { + c.Edit(a.DB, w, r) +} + +func (a *App) Run(host string) { + log.Fatal(http.ListenAndServe(host, a.Router)) +} diff --git a/controllers/auth.go b/controllers/auth.go new file mode 100644 index 000000000..77701cbff --- /dev/null +++ b/controllers/auth.go @@ -0,0 +1,73 @@ +package controllers + +import ( + "context" + "fmt" + "net/http" + "os" + "strings" + + "github.com/manabie-com/togo/models" + u "github.com/manabie-com/togo/utils" + + "github.com/dgrijalva/jwt-go" +) + +var JwtAuthentication = func(next http.Handler) http.Handler { + + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + + notAuth := []string{"/api/user/new", "/api/user/login"} //List of endpoints that doesn't require auth + requestPath := r.URL.Path //current request path + + //check if request does not need authentication, serve the request if it doesn't need it + for _, value := range notAuth { + + if value == requestPath { + next.ServeHTTP(w, r) + return + } + } + + tokenHeader := r.Header.Get("Authorization") //Grab the token from the header + var response map[string]interface{} + if tokenHeader == "" { //Token is missing, returns with error code 403 Unauthorized + response = u.Message(false, "Missing auth token") + u.Respond(w, http.StatusForbidden, response) + return + } + + splitted := strings.Split(tokenHeader, " ") //The token normally comes in format `Bearer {token-body}`, we check if the retrieved token matched this requirement + if len(splitted) != 2 { + response = u.Message(false, "Invalid/Malformed auth token") + w.Header().Add("Content-Type", "application/json") + u.Respond(w, http.StatusForbidden, response) + return + } + + tokenPart := splitted[1] //Grab the token part, what we are truly interested in + tk := &models.Token{} + + token, err := jwt.ParseWithClaims(tokenPart, tk, func(token *jwt.Token) (interface{}, error) { + return []byte(os.Getenv("token_password")), nil + }) + + if err != nil { //Malformed token, returns with http code 403 as usual + response = u.Message(false, "Malformed authentication token") + u.Respond(w, http.StatusForbidden, response) + return + } + + if !token.Valid { //Token is invalid, maybe not signed on this server + response = u.Message(false, "Token is not valid.") + u.Respond(w, http.StatusForbidden, response) + return + } + + //Everything went well, proceed with the request and set the caller to the user retrieved from the parsed token + fmt.Printf("User %v", tk.UserId) //Useful for monitoring + ctx := context.WithValue(r.Context(), "user", tk.UserId) + r = r.WithContext(ctx) + next.ServeHTTP(w, r) //proceed in the middleware chain! + }) +} diff --git a/controllers/task.go b/controllers/task.go index 8bec52481..603f332b5 100644 --- a/controllers/task.go +++ b/controllers/task.go @@ -1,16 +1,17 @@ package controllers import ( + "database/sql" "net/http" u "github.com/manabie-com/togo/utils" ) -var GetTasks = func(w http.ResponseWriter, r *http.Request) { +var GetTasks = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { u.Respond(w, http.StatusOK, map[string]interface{}{}) } -var GetTask = func(w http.ResponseWriter, r *http.Request) { +var GetTask = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { // check taskID exist // if not exists u.Respond(w, http.StatusNotFound, map[string]interface{}{}) @@ -18,7 +19,7 @@ var GetTask = func(w http.ResponseWriter, r *http.Request) { u.Respond(w, http.StatusOK, map[string]interface{}{}) } -var Add = func(w http.ResponseWriter, r *http.Request) { +var Add = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { // check task number today greater than or equal to current user limitDayTasks // if true u.Respond(w, http.StatusNotAcceptable, map[string]interface{}{}) @@ -27,7 +28,7 @@ var Add = func(w http.ResponseWriter, r *http.Request) { u.Respond(w, http.StatusCreated, map[string]interface{}{}) } -var Edit = func(w http.ResponseWriter, r *http.Request) { +var Edit = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { // check taskID exist // if not exists u.Respond(w, http.StatusNotFound, map[string]interface{}{}) diff --git a/controllers/user.go b/controllers/user.go index 127df163c..25d894a77 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -1,20 +1,36 @@ package controllers import ( + "database/sql" + "encoding/json" + "fmt" "net/http" + "github.com/manabie-com/togo/models" u "github.com/manabie-com/togo/utils" ) -var SignUp = func(w http.ResponseWriter, r *http.Request) { - - // send token jwt +var SignUp = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { + user := &models.User{} + err := json.NewDecoder(r.Body).Decode(user) + if err != nil { + u.Respond(w, http.StatusBadRequest, u.Message(false, "Had field invalid")) + } + defer db.Close() + fmt.Println(user) + var userId int + err = db.QueryRow(`INSERT INTO users(name, email, password) VALUES($1,$2,$3) RETURNING id;`, user.Name, user.Email, user.Password).Scan(&userId) + fmt.Println(userId) + if err != nil { + u.Respond(w, http.StatusBadRequest, u.Message(false, "Invalid request")) + } + // send token jwt here // ... - u.Respond(w, http.StatusCreated, map[string]interface{}{}) - + u.Respond(w, http.StatusCreated, u.Message(true, "Created Account")) } -var Login = func(w http.ResponseWriter, r *http.Request) { +var Login = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { + // get email password // if email and password not exist u.Respond(w, http.StatusBadRequest, map[string]interface{}{}) @@ -25,14 +41,14 @@ var Login = func(w http.ResponseWriter, r *http.Request) { u.Respond(w, http.StatusOK, map[string]interface{}{}) } -var GetMe = func(w http.ResponseWriter, r *http.Request) { +var GetMe = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { // if jwt invalid u.Respond(w, http.StatusBadRequest, map[string]interface{}{}) // decode jwt -> get userID -> get user u.Respond(w, http.StatusOK, map[string]interface{}{}) } -var UpdateMe = func(w http.ResponseWriter, r *http.Request) { +var UpdateMe = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { // success u.Respond(w, http.StatusOK, map[string]interface{}{}) } diff --git a/go.mod b/go.mod index c35618cf1..8bdefe153 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,8 @@ go 1.18 require ( github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect + github.com/go-pg/pg/v10 v10.10.6 // indirect + github.com/go-pg/zerochecker v0.2.0 // indirect github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect github.com/go-playground/validator/v10 v10.11.0 // indirect @@ -12,8 +14,15 @@ require ( github.com/jinzhu/now v1.1.5 // indirect github.com/joho/godotenv v1.4.0 // indirect github.com/leodido/go-urn v1.2.1 // indirect + github.com/lib/pq v1.10.6 // indirect + github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect + github.com/vmihailenco/bufpool v0.1.11 // indirect + github.com/vmihailenco/msgpack/v5 v5.3.4 // indirect + github.com/vmihailenco/tagparser v0.1.2 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect - golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 // indirect + golang.org/x/sys v0.0.0-20210923061019-b8560ed6a9b7 // indirect golang.org/x/text v0.3.7 // indirect gorm.io/gorm v1.23.6 // indirect + mellium.im/sasl v0.2.1 // indirect ) diff --git a/go.sum b/go.sum index a1b57c703..d27901f4d 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,20 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/go-pg/pg/v10 v10.10.6 h1:1vNtPZ4Z9dWUw/TjJwOfFUbF5nEq1IkR6yG8Mq/Iwso= +github.com/go-pg/pg/v10 v10.10.6/go.mod h1:GLmFXufrElQHf5uzM3BQlcfwV3nsgnHue5uzjQ6Nqxg= +github.com/go-pg/zerochecker v0.2.0 h1:pp7f72c3DobMWOb2ErtZsnrPaSvHd2W4o9//8HtF4mU= +github.com/go-pg/zerochecker v0.2.0/go.mod h1:NJZ4wKL0NmTtz0GKCoJ8kym6Xn/EQzXRl2OnAe7MmDo= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= @@ -10,8 +22,27 @@ github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/j github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw= github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= @@ -27,31 +58,121 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs= +github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/vmihailenco/bufpool v0.1.11 h1:gOq2WmBrq0i2yW5QJ16ykccQ4wH9UyEsgLm6czKAd94= +github.com/vmihailenco/bufpool v0.1.11/go.mod h1:AFf/MOy3l2CFTKbxwt0mp2MwnqjNEs5H/UxrkA5jxTQ= +github.com/vmihailenco/msgpack/v5 v5.3.4 h1:qMKAwOV+meBw2Y8k9cVwAy7qErtYCwBzZ2ellBfvnqc= +github.com/vmihailenco/msgpack/v5 v5.3.4/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc= +github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 h1:siQdpVirKtzPhKl3lZWozZraCFObP8S1v6PRp0bLrtU= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210923061019-b8560ed6a9b7 h1:c20P3CcPbopVp2f7099WLOqSNKURf30Z0uq66HpijZY= +golang.org/x/sys v0.0.0-20210923061019-b8560ed6a9b7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/gorm v1.23.6 h1:KFLdNgri4ExFFGTRGGFWON2P1ZN28+9SJRN8voOoYe0= gorm.io/gorm v1.23.6/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +mellium.im/sasl v0.2.1 h1:nspKSRg7/SyO0cRGY71OkfHab8tf9kCts6a6oTDut0w= +mellium.im/sasl v0.2.1/go.mod h1:ROaEDLQNuf9vjKqE1SrAfnsobm2YKXT1gnN1uDp1PjQ= diff --git a/main.go b/main.go index d00aae02d..c173833b2 100644 --- a/main.go +++ b/main.go @@ -1,37 +1,23 @@ package main import ( - "fmt" - "net/http" - "os" + "log" + "syscall" - "github.com/gorilla/mux" - "github.com/manabie-com/togo/controllers" + "github.com/joho/godotenv" + _ "github.com/lib/pq" + "github.com/manabie-com/togo/app" ) func main() { - router := mux.NewRouter() - - // sub router like http://:/api/users - userRouter := router.PathPrefix("/api/users").Subrouter() - userRouter.HandleFunc("/", controllers.GetMe).Methods("GET") - userRouter.HandleFunc("/signUp", controllers.SignUp).Methods("POST") - userRouter.HandleFunc("/login", controllers.Login).Methods("POST") - userRouter.HandleFunc("/", controllers.UpdateMe).Methods("PATCH") - // sub router like http://:/api/tasks - taskRouter := router.PathPrefix("/api/tasks").Subrouter() - taskRouter.HandleFunc("/", controllers.GetTasks).Methods("GET") - taskRouter.HandleFunc("/{id}", controllers.GetTask).Methods("GET") - taskRouter.HandleFunc("/add", controllers.Add).Methods("POST") - taskRouter.HandleFunc("/{id}", controllers.Edit).Methods("PATCH") - - port := os.Getenv("PORT") - if port == "" { - port = "8000" + if err := godotenv.Load(); err != nil { + log.Fatal("Error loading .env file") } - fmt.Println(port) - err := http.ListenAndServe(":"+port, router) - if err != nil { - fmt.Print(err) + port, ok := syscall.Getenv("PORT") + if !ok { + port = "8000" } + app := &app.App{} + app.Init() + app.Run(":" + port) } diff --git a/models/user.go b/models/user.go index ff51b3613..09eccac2f 100644 --- a/models/user.go +++ b/models/user.go @@ -1,20 +1,16 @@ package models -type User struct { - ID uint32 `json:"id"` - Email string `json:"email" validate:"required"` - Name string `json:"name" validate:"required"` - Password string `json:"password" validate:"required"` - IsPayment bool `json:"isPayment"` - LimitDayTasks uint `json:"limitDayTasks"` - Tasks *[]Task `json:"tasks"` -} +import "github.com/dgrijalva/jwt-go" -func (u *User) validate() error { - // v := validator.New() - // err := v.Struct(u) - // for _, e := range err.(validator.ValidationErrors) { - // fmt.Print(e) - // } - return nil +type Token struct { + UserId uint + jwt.StandardClaims +} +type User struct { + ID uint32 `json:"id"` + Email string `json:"email" validate:"required, email"` + Name string `json:"name" validate:"required"` + Password string `json:"password" validate:"required"` + IsPayment bool `json:"isPayment"` + LimitDayTasks uint `json:"limitDayTasks"` } diff --git a/utils/utils.go b/utils/utils.go index ebe98c1ab..778bfc9fe 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -5,8 +5,11 @@ import ( "net/http" ) -func Respond(w http.ResponseWriter, status int, data map[string]interface{}) { - w.Header().Set("Content-Type", "application/json") +func Message(status bool, message string) map[string]interface{} { + return map[string]interface{}{"status": status, "message": message} +} + +func Respond(w http.ResponseWriter, status int, payload map[string]interface{}) { w.WriteHeader(status) - json.NewEncoder(w).Encode(data) + json.NewEncoder(w).Encode(payload) } From 435a53efd00eb402ed59f49c42526aa5c6d14df5 Mon Sep 17 00:00:00 2001 From: Quang Ha Date: Thu, 23 Jun 2022 00:20:12 +0700 Subject: [PATCH 05/41] litte update --- .vscode/launch.json | 40 +++++++++++++++++++++ app/app.go | 16 ++++----- controllers/auth.go | 16 +++------ controllers/task.go | 64 +++++++++++++++++---------------- controllers/user.go | 87 +++++++++++++++++++++++++++++++-------------- models/task.go | 17 --------- utils/utils.go | 17 ++++++--- 7 files changed, 159 insertions(+), 98 deletions(-) create mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..495ebf67a --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,40 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + + { + "name": "Attach to Process", + "type": "go", + "request": "attach", + "mode": "local", + "processId": 0 + }, + + { + "name": "Launch file", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "${file}" + }, + { + "name": "Connect to server", + "type": "go", + "request": "attach", + "mode": "remote", + "remotePath": "${workspaceFolder}", + "port": 2345, + "host": "127.0.0.1" + }, + { + "name": "Launch Package", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceRoot}" + } + ] +} \ No newline at end of file diff --git a/app/app.go b/app/app.go index 60e3b2f52..98a7ed216 100644 --- a/app/app.go +++ b/app/app.go @@ -26,7 +26,6 @@ func (a *App) Init() { if err != nil { log.Fatal(err) } - defer db.Close() fmt.Println("Database is connected!") a.DB = db @@ -39,7 +38,7 @@ func (a *App) Routes() { router := a.Router // sub router like http://:/api/users userRouter := router.PathPrefix("/api/users").Subrouter() - userRouter.HandleFunc("/", a.GetMe).Methods("GET") + userRouter.HandleFunc("/me", a.GetMe).Methods("GET") userRouter.HandleFunc("/signup", a.SignUp).Methods("POST") userRouter.HandleFunc("/login", a.Login).Methods("POST") userRouter.HandleFunc("/", a.UpdateMe).Methods("PATCH") @@ -53,6 +52,7 @@ func (a *App) Routes() { } func (a *App) GetMe(w http.ResponseWriter, r *http.Request) { + fmt.Println("From GetMe Function") c.GetMe(a.DB, w, r) } @@ -61,27 +61,27 @@ func (a *App) SignUp(w http.ResponseWriter, r *http.Request) { } func (a *App) Login(w http.ResponseWriter, r *http.Request) { - c.Login(a.DB, w, r) + // c.Login(a.DB, w, r) } func (a *App) UpdateMe(w http.ResponseWriter, r *http.Request) { - c.UpdateMe(a.DB, w, r) + // c.UpdateMe(a.DB, w, r) } func (a *App) GetTasks(w http.ResponseWriter, r *http.Request) { - c.GetTasks(a.DB, w, r) + // c.GetTasks(a.DB, w, r) } func (a *App) GetTask(w http.ResponseWriter, r *http.Request) { - c.GetTask(a.DB, w, r) + // c.GetTask(a.DB, w, r) } func (a *App) Add(w http.ResponseWriter, r *http.Request) { - c.Add(a.DB, w, r) + // c.Add(a.DB, w, r) } func (a *App) Edit(w http.ResponseWriter, r *http.Request) { - c.Edit(a.DB, w, r) + // c.Edit(a.DB, w, r) } func (a *App) Run(host string) { diff --git a/controllers/auth.go b/controllers/auth.go index 77701cbff..08d99012c 100644 --- a/controllers/auth.go +++ b/controllers/auth.go @@ -30,18 +30,14 @@ var JwtAuthentication = func(next http.Handler) http.Handler { } tokenHeader := r.Header.Get("Authorization") //Grab the token from the header - var response map[string]interface{} - if tokenHeader == "" { //Token is missing, returns with error code 403 Unauthorized - response = u.Message(false, "Missing auth token") - u.Respond(w, http.StatusForbidden, response) + if tokenHeader == "" { //Token is missing, returns with error code 403 Unauthorized + u.Respond(w, http.StatusForbidden, "Failure", "Missing auth token", nil) return } splitted := strings.Split(tokenHeader, " ") //The token normally comes in format `Bearer {token-body}`, we check if the retrieved token matched this requirement if len(splitted) != 2 { - response = u.Message(false, "Invalid/Malformed auth token") - w.Header().Add("Content-Type", "application/json") - u.Respond(w, http.StatusForbidden, response) + u.Respond(w, http.StatusForbidden, "Failure", "Invalid/Malformed auth token", nil) return } @@ -53,14 +49,12 @@ var JwtAuthentication = func(next http.Handler) http.Handler { }) if err != nil { //Malformed token, returns with http code 403 as usual - response = u.Message(false, "Malformed authentication token") - u.Respond(w, http.StatusForbidden, response) + u.Respond(w, http.StatusForbidden, "Failure", "Malformed authentication token", nil) return } if !token.Valid { //Token is invalid, maybe not signed on this server - response = u.Message(false, "Token is not valid.") - u.Respond(w, http.StatusForbidden, response) + u.Respond(w, http.StatusForbidden, "Failure", "Token is not valid.", nil) return } diff --git a/controllers/task.go b/controllers/task.go index 603f332b5..6affb6ca0 100644 --- a/controllers/task.go +++ b/controllers/task.go @@ -1,37 +1,39 @@ package controllers -import ( - "database/sql" - "net/http" +// var GetTasks = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { +// u.Respond(w, http.StatusOK, map[string]interface{}{}) +// } - u "github.com/manabie-com/togo/utils" -) +// var GetTask = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { +// // check taskID exist +// // if not exists +// u.Respond(w, http.StatusNotFound, map[string]interface{}{}) +// // else +// u.Respond(w, http.StatusOK, map[string]interface{}{}) +// } -var GetTasks = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { - u.Respond(w, http.StatusOK, map[string]interface{}{}) -} +// var Add = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { +// // decode userid from jwt => userId +// task := &models.Task{ +// CreatedAt: time.Now().UTC(), +// } +// err := json.NewDecoder(r.Body).Decode(task) +// if err != nil { +// u.Respond(w, http.StatusBadRequest, u.Message(false, "invalid request")) +// } -var GetTask = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { - // check taskID exist - // if not exists - u.Respond(w, http.StatusNotFound, map[string]interface{}{}) - // else - u.Respond(w, http.StatusOK, map[string]interface{}{}) -} +// // check task number today greater than or equal to current user limitDayTasks +// // if true +// u.Respond(w, http.StatusNotAcceptable, map[string]interface{}{}) +// // else +// // update task number today += 1 +// u.Respond(w, http.StatusCreated, map[string]interface{}{}) +// } -var Add = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { - // check task number today greater than or equal to current user limitDayTasks - // if true - u.Respond(w, http.StatusNotAcceptable, map[string]interface{}{}) - // else - // update task number today += 1 - u.Respond(w, http.StatusCreated, map[string]interface{}{}) -} - -var Edit = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { - // check taskID exist - // if not exists - u.Respond(w, http.StatusNotFound, map[string]interface{}{}) - // else - u.Respond(w, http.StatusOK, map[string]interface{}{}) -} +// var Edit = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { +// // check taskID exist +// // if not exists +// u.Respond(w, http.StatusNotFound, map[string]interface{}{}) +// // else +// u.Respond(w, http.StatusOK, map[string]interface{}{}) +// } diff --git a/controllers/user.go b/controllers/user.go index 25d894a77..634ecc721 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -10,45 +10,80 @@ import ( u "github.com/manabie-com/togo/utils" ) -var SignUp = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { - user := &models.User{} +var SignUp = func(db *sql.DB, w http.ResponseWriter, r *http.Request) interface{} { + defer db.Close() + user := &models.User{ + IsPayment: false, + LimitDayTasks: 10, + } + err := json.NewDecoder(r.Body).Decode(user) if err != nil { - u.Respond(w, http.StatusBadRequest, u.Message(false, "Had field invalid")) + u.Respond(w, http.StatusBadRequest, "Failure", "Invalid request, Please check your input fields!", nil) + return err } - defer db.Close() - fmt.Println(user) - var userId int - err = db.QueryRow(`INSERT INTO users(name, email, password) VALUES($1,$2,$3) RETURNING id;`, user.Name, user.Email, user.Password).Scan(&userId) - fmt.Println(userId) + + results, err := db.Exec(`INSERT INTO users(name, email, password) VALUES($1,$2,$3) RETURNING id`, user.Name, user.Email, user.Password) if err != nil { - u.Respond(w, http.StatusBadRequest, u.Message(false, "Invalid request")) + u.Respond(w, http.StatusBadRequest, "Failure", "OOPS, something were wrong, please try again", nil) + return err } // send token jwt here // ... - u.Respond(w, http.StatusCreated, u.Message(true, "Created Account")) + u.Respond(w, http.StatusCreated, "Success", "Created Account", results) + return nil } -var Login = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { +// var Login = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { - // get email password - // if email and password not exist - u.Respond(w, http.StatusBadRequest, map[string]interface{}{}) - // if email exist and password incorrect - u.Respond(w, http.StatusUnauthorized, map[string]interface{}{}) - // if email and password OK - // send token to client - u.Respond(w, http.StatusOK, map[string]interface{}{}) -} +// // get email password +// // if email and password not exist +// u.Respond(w, http.StatusBadRequest, map[string]interface{}{}) +// // if email exist and password incorrect +// u.Respond(w, http.StatusUnauthorized, map[string]interface{}{}) +// // if email and password OK +// // send token to client +// u.Respond(w, http.StatusOK, map[string]interface{}{}) +// } var GetMe = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { + // decode jwt => userId + + // data := db.QueryRow("SELECT name, email, is_payment, limit_day_tasks FROM users WHERE id = $1", 1) + var id int = 1 + rows, err := db.Query(`SELECT name, email, is_payment FROM users WHERE id = $1`, id) + if err != nil { + fmt.Println(err) + } + + var user models.User + + for rows.Next() { + var ( + id int = 1 + name string + email string + ) + err = rows.Scan(&id, &name, &email) + if err != nil { + fmt.Println(err) + } + fmt.Println(user) + // check errors + user = models.User{Name: name, Email: email} + fmt.Println(user) + } + + // var user User + + u.Respond(w, http.StatusOK, "Success", "Success", user) // if jwt invalid - u.Respond(w, http.StatusBadRequest, map[string]interface{}{}) + // u.Respond(w, http.StatusBadRequest, , ) // decode jwt -> get userID -> get user - u.Respond(w, http.StatusOK, map[string]interface{}{}) + // u.Respond(w, http.StatusOK, map[string]interface{}{}) } -var UpdateMe = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { - // success - u.Respond(w, http.StatusOK, map[string]interface{}{}) -} +// var UpdateMe = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { +// // success +// u.Respond(w, http.StatusOK, map[string]interface{}{}) +// } diff --git a/models/task.go b/models/task.go index bdce0a377..41144dfe3 100644 --- a/models/task.go +++ b/models/task.go @@ -6,23 +6,6 @@ type Task struct { ID int32 `json:"id"` Name string `json:"name" validate:"required"` Content string `json:"content"` - Status Status `json:"status"` CreatedAt time.Time `json:"createdAt"` UserId int32 `json:"userId"` } - -type Status string - -const ( - toDo Status = "todo" - doing Status = "doing" - done Status = "done" -) - -func (e Status) isValidStatus() bool { - switch e { - case toDo, doing, done: - return true - } - return false -} diff --git a/utils/utils.go b/utils/utils.go index 778bfc9fe..7c34c2b87 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -5,11 +5,18 @@ import ( "net/http" ) -func Message(status bool, message string) map[string]interface{} { - return map[string]interface{}{"status": status, "message": message} +type response struct { + status string + message string + data interface{} } -func Respond(w http.ResponseWriter, status int, payload map[string]interface{}) { - w.WriteHeader(status) - json.NewEncoder(w).Encode(payload) +func Respond(w http.ResponseWriter, statusCode int, status, message string, data interface{}) { + res := response{ + status: status, + message: message, + data: data, + } + w.WriteHeader(statusCode) + json.NewEncoder(w).Encode(res) } From 356407f92f8a7d36cf676ad173ca89823c7831f6 Mon Sep 17 00:00:00 2001 From: Quang Ha Date: Thu, 23 Jun 2022 17:50:38 +0700 Subject: [PATCH 06/41] implements users, task controller and add middleware --- app/app.go | 13 ++-- controllers/auth.go | 19 ++---- controllers/task.go | 100 +++++++++++++++++++++++-------- controllers/user.go | 141 +++++++++++++++++++++++++++----------------- main.go | 1 + models/task.go | 2 +- models/user.go | 19 +++--- utils/utils.go | 12 ++-- 8 files changed, 195 insertions(+), 112 deletions(-) diff --git a/app/app.go b/app/app.go index 98a7ed216..59f705ca7 100644 --- a/app/app.go +++ b/app/app.go @@ -9,6 +9,7 @@ import ( "github.com/gorilla/mux" "github.com/joho/godotenv" + "github.com/manabie-com/togo/controllers" c "github.com/manabie-com/togo/controllers" ) @@ -26,7 +27,10 @@ func (a *App) Init() { if err != nil { log.Fatal(err) } - + err = db.Ping() + if err != nil { + panic(err) + } fmt.Println("Database is connected!") a.DB = db a.Router = mux.NewRouter() @@ -49,10 +53,11 @@ func (a *App) Routes() { taskRouter.HandleFunc("/add", a.Add).Methods("POST") taskRouter.HandleFunc("/{id}", a.Edit).Methods("PATCH") // runs database + router.Use(mux.CORSMethodMiddleware(router)) + router.Use(controllers.JwtAuthentication) } func (a *App) GetMe(w http.ResponseWriter, r *http.Request) { - fmt.Println("From GetMe Function") c.GetMe(a.DB, w, r) } @@ -61,11 +66,11 @@ func (a *App) SignUp(w http.ResponseWriter, r *http.Request) { } func (a *App) Login(w http.ResponseWriter, r *http.Request) { - // c.Login(a.DB, w, r) + c.Login(a.DB, w, r) } func (a *App) UpdateMe(w http.ResponseWriter, r *http.Request) { - // c.UpdateMe(a.DB, w, r) + c.UpdateMe(a.DB, w, r) } func (a *App) GetTasks(w http.ResponseWriter, r *http.Request) { diff --git a/controllers/auth.go b/controllers/auth.go index 08d99012c..68ecac465 100644 --- a/controllers/auth.go +++ b/controllers/auth.go @@ -2,7 +2,6 @@ package controllers import ( "context" - "fmt" "net/http" "os" "strings" @@ -14,15 +13,13 @@ import ( ) var JwtAuthentication = func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - notAuth := []string{"/api/user/new", "/api/user/login"} //List of endpoints that doesn't require auth - requestPath := r.URL.Path //current request path + notAuth := []string{"/api/users/signup", "/api/users/login"} //List of endpoints that doesn't require auth + requestPath := r.URL.Path //current request path //check if request does not need authentication, serve the request if it doesn't need it for _, value := range notAuth { - if value == requestPath { next.ServeHTTP(w, r) return @@ -43,13 +40,11 @@ var JwtAuthentication = func(next http.Handler) http.Handler { tokenPart := splitted[1] //Grab the token part, what we are truly interested in tk := &models.Token{} - token, err := jwt.ParseWithClaims(tokenPart, tk, func(token *jwt.Token) (interface{}, error) { - return []byte(os.Getenv("token_password")), nil + return []byte(os.Getenv("SECRET_TOKEN")), nil }) - if err != nil { //Malformed token, returns with http code 403 as usual - u.Respond(w, http.StatusForbidden, "Failure", "Malformed authentication token", nil) + u.Respond(w, http.StatusForbidden, "Failure", "Malformed authentication token", err.Error()) return } @@ -57,11 +52,9 @@ var JwtAuthentication = func(next http.Handler) http.Handler { u.Respond(w, http.StatusForbidden, "Failure", "Token is not valid.", nil) return } - //Everything went well, proceed with the request and set the caller to the user retrieved from the parsed token - fmt.Printf("User %v", tk.UserId) //Useful for monitoring + //Useful for monitoring ctx := context.WithValue(r.Context(), "user", tk.UserId) - r = r.WithContext(ctx) - next.ServeHTTP(w, r) //proceed in the middleware chain! + next.ServeHTTP(w, r.WithContext(ctx)) //proceed in the middleware chain! }) } diff --git a/controllers/task.go b/controllers/task.go index 6affb6ca0..b327bef50 100644 --- a/controllers/task.go +++ b/controllers/task.go @@ -1,8 +1,33 @@ package controllers -// var GetTasks = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { -// u.Respond(w, http.StatusOK, map[string]interface{}{}) -// } +import ( + "database/sql" + "encoding/json" + "net/http" + "time" + + "github.com/go-playground/validator/v10" + "github.com/manabie-com/togo/models" + u "github.com/manabie-com/togo/utils" +) + +var GetTasks = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { + // get user id here + var userId = 1 + rows, err := db.Query(`SELECT name, content, created_at FROM tasks WHERE user_id = $1`, userId) + if err != nil { + u.Respond(w, http.StatusNotFound, "Failure", err.Error(), nil) + return + } + var tasks []*models.Task + + for rows.Next() { + var task = &models.Task{} + rows.Scan(&task.Name, &task.Content, &task.CreatedAt) + tasks = append(tasks, task) + } + u.Respond(w, http.StatusOK, "Success", "Success", tasks) +} // var GetTask = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { // // check taskID exist @@ -12,28 +37,53 @@ package controllers // u.Respond(w, http.StatusOK, map[string]interface{}{}) // } -// var Add = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { -// // decode userid from jwt => userId -// task := &models.Task{ -// CreatedAt: time.Now().UTC(), -// } -// err := json.NewDecoder(r.Body).Decode(task) -// if err != nil { -// u.Respond(w, http.StatusBadRequest, u.Message(false, "invalid request")) -// } - -// // check task number today greater than or equal to current user limitDayTasks -// // if true -// u.Respond(w, http.StatusNotAcceptable, map[string]interface{}{}) -// // else -// // update task number today += 1 -// u.Respond(w, http.StatusCreated, map[string]interface{}{}) -// } +var Add = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { + // decode userid from jwt => userId + task := &models.Task{ + CreatedAt: time.Now().UTC(), + UserId: 1, + } + err := json.NewDecoder(r.Body).Decode(task) + if err != nil { + u.Respond(w, http.StatusBadRequest, "Failure", "invalid request", nil) + return + } + validate := validator.New() + + if err = validate.Struct(task); err != nil { + u.Respond(w, http.StatusBadRequest, "Failure", err.Error(), nil) + return + } + // insert database + err = db.QueryRow(`INSERT INTO tasks(name, content, created_at, user_id) VALUES($1, $2, $3, $4, $5) RETURNING name, email`, task.Name, task.Content, task.CreatedAt, task.UserId).Scan(&task.Name, &task.Content) + if err != nil { + u.Respond(w, http.StatusBadRequest, "Failure", err.Error(), nil) + return + } + u.Respond(w, http.StatusCreated, "Success", "Success creates task", map[string]interface{}{ + "name": task.Name, + "content": task.Content, + "created_at": task.CreatedAt, + }) + + // check task number today greater than or equal to current task limitDayTasks + // if true + // u.Respond(w, http.StatusNotAcceptable, map[string]interface{}{}) + // // else + // // update task number today += 1 + // u.Respond(w, http.StatusCreated, map[string]interface{}{}) +} // var Edit = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { -// // check taskID exist -// // if not exists -// u.Respond(w, http.StatusNotFound, map[string]interface{}{}) -// // else -// u.Respond(w, http.StatusOK, map[string]interface{}{}) +// id := r.URL.Query().Get("id") +// err := db.QueryRow(`UPDATE tasks SET name = $1 WHERE id = 3; RETURNING name, email`, id).Scan(&task.Name, &task.Content) +// if err != nil { +// u.Respond(w, http.StatusNotFound, "Failure", err.Error(), nil) +// return +// } +// check taskID exist +// if not exists +// u.Respond(w, http.StatusNotFound, map[string]interface{}{}) +// // else +// u.Respond(w, http.StatusOK, map[string]interface{}{}) // } diff --git a/controllers/user.go b/controllers/user.go index 634ecc721..fecf919f8 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -5,85 +5,116 @@ import ( "encoding/json" "fmt" "net/http" + "os" + "github.com/dgrijalva/jwt-go" + "github.com/go-playground/validator/v10" "github.com/manabie-com/togo/models" u "github.com/manabie-com/togo/utils" ) -var SignUp = func(db *sql.DB, w http.ResponseWriter, r *http.Request) interface{} { - defer db.Close() +var SignUp = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { user := &models.User{ IsPayment: false, LimitDayTasks: 10, } - + // validate err := json.NewDecoder(r.Body).Decode(user) if err != nil { - u.Respond(w, http.StatusBadRequest, "Failure", "Invalid request, Please check your input fields!", nil) - return err + u.Respond(w, http.StatusBadRequest, "Failure", err.Error(), nil) + return } - results, err := db.Exec(`INSERT INTO users(name, email, password) VALUES($1,$2,$3) RETURNING id`, user.Name, user.Email, user.Password) + validate := validator.New() + + if err = validate.Struct(user); err != nil { + u.Respond(w, http.StatusBadRequest, "Failure", err.Error(), nil) + return + } + // insert database + err = db.QueryRow(`INSERT INTO users(name, email, password) VALUES($1, $2, $3) RETURNING id, name, email`, user.Name, user.Email, user.Password).Scan(&user.ID, &user.Name, &user.Email) if err != nil { - u.Respond(w, http.StatusBadRequest, "Failure", "OOPS, something were wrong, please try again", nil) - return err + u.Respond(w, http.StatusBadRequest, "Failure", err.Error(), nil) + return } + fmt.Println(user) // send token jwt here - // ... - u.Respond(w, http.StatusCreated, "Success", "Created Account", results) - return nil + tk := &models.Token{UserId: user.ID} + token := jwt.NewWithClaims(jwt.GetSigningMethod("HS256"), tk) + tokenString, _ := token.SignedString([]byte(os.Getenv("SECRET_TOKEN"))) + // response and send token to client + u.Respond(w, http.StatusCreated, "Success", "Created Account", map[string]interface{}{ + "name": user.Name, + "email": user.Email, + "token": tokenString, + }) } -// var Login = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { - -// // get email password -// // if email and password not exist -// u.Respond(w, http.StatusBadRequest, map[string]interface{}{}) -// // if email exist and password incorrect -// u.Respond(w, http.StatusUnauthorized, map[string]interface{}{}) -// // if email and password OK -// // send token to client -// u.Respond(w, http.StatusOK, map[string]interface{}{}) -// } - -var GetMe = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { - // decode jwt => userId - - // data := db.QueryRow("SELECT name, email, is_payment, limit_day_tasks FROM users WHERE id = $1", 1) - var id int = 1 - rows, err := db.Query(`SELECT name, email, is_payment FROM users WHERE id = $1`, id) +var Login = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { + user := &models.User{} + // validate + err := json.NewDecoder(r.Body).Decode(user) if err != nil { - fmt.Println(err) + u.Respond(w, http.StatusBadRequest, "Failure", err.Error(), nil) + return } + var ( + email string + password string + ) + err = db.QueryRow(`SELECT id, name, email, password FROM users WHERE email = $1`, user.Email).Scan(&user.ID, &user.Name, &email, &password) + if err != nil { + u.Respond(w, http.StatusNotFound, "Failure", "Your email invalid", nil) + return + } + // if email exist and password incorrect + if email != user.Email || password != user.Password { + u.Respond(w, http.StatusUnauthorized, "Failure", "Password incorrect", nil) + return + } + // if email and password OK + //Create JWT token + fmt.Println(user) + tk := &models.Token{UserId: user.ID} + token := jwt.NewWithClaims(jwt.GetSigningMethod("HS256"), tk) + fmt.Println(os.Getenv("SECRET_TOKEN")) + tokenString, _ := token.SignedString([]byte(os.Getenv("SECRET_TOKEN"))) + // response and send token to client + u.Respond(w, http.StatusOK, "Success", "Login Success", map[string]interface{}{ + "name": user.Name, + "email": user.Email, + "token": tokenString, + }) +} - var user models.User +var GetMe = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { + var userID = r.Context().Value("user").(uint32) - for rows.Next() { - var ( - id int = 1 - name string - email string - ) - err = rows.Scan(&id, &name, &email) - if err != nil { - fmt.Println(err) - } - fmt.Println(user) - // check errors - user = models.User{Name: name, Email: email} - fmt.Println(user) + var user = &models.User{ + ID: userID, } - // var user User + err := db.QueryRow(`SELECT name, email, is_payment, limit_day_tasks FROM users WHERE id = $1`, user.ID).Scan(&user.Name, &user.Email, &user.IsPayment, &user.LimitDayTasks) - u.Respond(w, http.StatusOK, "Success", "Success", user) - // if jwt invalid - // u.Respond(w, http.StatusBadRequest, , ) - // decode jwt -> get userID -> get user - // u.Respond(w, http.StatusOK, map[string]interface{}{}) + if err != nil { + u.Respond(w, http.StatusNotFound, "Failure", err.Error(), nil) + return + } + u.Respond(w, http.StatusOK, "Success", "Success", map[string]interface{}{ + "name": user.Name, + "email": user.Email, + "is_payment": user.IsPayment, + "limit_day_tasks": user.LimitDayTasks, + }) } -// var UpdateMe = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { -// // success -// u.Respond(w, http.StatusOK, map[string]interface{}{}) -// } +var UpdateMe = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { + user := models.User{} + // success + u.Respond(w, http.StatusOK, "Success", "Success", map[string]interface{}{ + "name": user.Name, + "email": user.Email, + "is_payment": user.IsPayment, + "limit_day_tasks": user.LimitDayTasks, + }) +} diff --git a/main.go b/main.go index c173833b2..7b778e03a 100644 --- a/main.go +++ b/main.go @@ -18,6 +18,7 @@ func main() { port = "8000" } app := &app.App{} + defer app.DB.Close() app.Init() app.Run(":" + port) } diff --git a/models/task.go b/models/task.go index 41144dfe3..84fad46e2 100644 --- a/models/task.go +++ b/models/task.go @@ -3,7 +3,7 @@ package models import "time" type Task struct { - ID int32 `json:"id"` + ID int32 `json:"id" validate:"omitempty"` Name string `json:"name" validate:"required"` Content string `json:"content"` CreatedAt time.Time `json:"createdAt"` diff --git a/models/user.go b/models/user.go index 09eccac2f..8529146ae 100644 --- a/models/user.go +++ b/models/user.go @@ -1,16 +1,19 @@ package models -import "github.com/dgrijalva/jwt-go" +import ( + "github.com/dgrijalva/jwt-go" +) type Token struct { - UserId uint + UserId uint32 jwt.StandardClaims } type User struct { - ID uint32 `json:"id"` - Email string `json:"email" validate:"required, email"` - Name string `json:"name" validate:"required"` - Password string `json:"password" validate:"required"` - IsPayment bool `json:"isPayment"` - LimitDayTasks uint `json:"limitDayTasks"` + ID uint32 `json:"id" validate:"omitempty"` + Email string `json:"email" validate:"required,email"` + Name string `json:"name" validate:"required,min=5,max=20"` + Password string `json:"password" validate:"required,min=6,max=20"` + IsPayment bool `json:"isPayment" validate:"omitempty"` + LimitDayTasks uint `json:"limitDayTasks" validate:"omitempty"` + Task *[]Task } diff --git a/utils/utils.go b/utils/utils.go index 7c34c2b87..fd977b911 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -6,16 +6,16 @@ import ( ) type response struct { - status string - message string - data interface{} + Status string + Message string + Data interface{} } func Respond(w http.ResponseWriter, statusCode int, status, message string, data interface{}) { res := response{ - status: status, - message: message, - data: data, + Status: status, + Message: message, + Data: data, } w.WriteHeader(statusCode) json.NewEncoder(w).Encode(res) From 4ebf8e37e56936d4ae4c76396a0df466eadd7769 Mon Sep 17 00:00:00 2001 From: Quang Ha Date: Fri, 24 Jun 2022 00:07:12 +0700 Subject: [PATCH 07/41] implement few controller functions --- app/app.go | 12 +++---- controllers/auth.go | 2 +- controllers/task.go | 76 +++++++++++++++++++++++++-------------------- controllers/user.go | 34 +++++++++++++------- models/task.go | 2 +- models/user.go | 3 +- 6 files changed, 75 insertions(+), 54 deletions(-) diff --git a/app/app.go b/app/app.go index 59f705ca7..3daaab944 100644 --- a/app/app.go +++ b/app/app.go @@ -45,10 +45,10 @@ func (a *App) Routes() { userRouter.HandleFunc("/me", a.GetMe).Methods("GET") userRouter.HandleFunc("/signup", a.SignUp).Methods("POST") userRouter.HandleFunc("/login", a.Login).Methods("POST") - userRouter.HandleFunc("/", a.UpdateMe).Methods("PATCH") + userRouter.HandleFunc("/edit", a.UpdateMe).Methods("PATCH") // sub router like http://:/api/tasks taskRouter := router.PathPrefix("/api/tasks").Subrouter() - taskRouter.HandleFunc("/", a.GetTasks).Methods("GET") + taskRouter.HandleFunc("", a.GetTasks).Methods("GET") taskRouter.HandleFunc("/{id}", a.GetTask).Methods("GET") taskRouter.HandleFunc("/add", a.Add).Methods("POST") taskRouter.HandleFunc("/{id}", a.Edit).Methods("PATCH") @@ -74,19 +74,19 @@ func (a *App) UpdateMe(w http.ResponseWriter, r *http.Request) { } func (a *App) GetTasks(w http.ResponseWriter, r *http.Request) { - // c.GetTasks(a.DB, w, r) + c.GetTasks(a.DB, w, r) } func (a *App) GetTask(w http.ResponseWriter, r *http.Request) { - // c.GetTask(a.DB, w, r) + c.GetTask(a.DB, w, r) } func (a *App) Add(w http.ResponseWriter, r *http.Request) { - // c.Add(a.DB, w, r) + c.Add(a.DB, w, r) } func (a *App) Edit(w http.ResponseWriter, r *http.Request) { - // c.Edit(a.DB, w, r) + c.Edit(a.DB, w, r) } func (a *App) Run(host string) { diff --git a/controllers/auth.go b/controllers/auth.go index 68ecac465..71e39c682 100644 --- a/controllers/auth.go +++ b/controllers/auth.go @@ -54,7 +54,7 @@ var JwtAuthentication = func(next http.Handler) http.Handler { } //Everything went well, proceed with the request and set the caller to the user retrieved from the parsed token //Useful for monitoring - ctx := context.WithValue(r.Context(), "user", tk.UserId) + ctx := context.WithValue(r.Context(), "user", tk) next.ServeHTTP(w, r.WithContext(ctx)) //proceed in the middleware chain! }) } diff --git a/controllers/task.go b/controllers/task.go index b327bef50..8d1fb7486 100644 --- a/controllers/task.go +++ b/controllers/task.go @@ -4,17 +4,17 @@ import ( "database/sql" "encoding/json" "net/http" - "time" "github.com/go-playground/validator/v10" + "github.com/gorilla/mux" "github.com/manabie-com/togo/models" u "github.com/manabie-com/togo/utils" ) var GetTasks = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { // get user id here - var userId = 1 - rows, err := db.Query(`SELECT name, content, created_at FROM tasks WHERE user_id = $1`, userId) + decoded := r.Context().Value("user").(*models.Token) + rows, err := db.Query(`SELECT name, content, created_at FROM tasks WHERE user_id = $1`, decoded.UserId) if err != nil { u.Respond(w, http.StatusNotFound, "Failure", err.Error(), nil) return @@ -29,19 +29,27 @@ var GetTasks = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { u.Respond(w, http.StatusOK, "Success", "Success", tasks) } -// var GetTask = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { -// // check taskID exist -// // if not exists -// u.Respond(w, http.StatusNotFound, map[string]interface{}{}) -// // else -// u.Respond(w, http.StatusOK, map[string]interface{}{}) -// } +var GetTask = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { + decoded := r.Context().Value("user").(*models.Token) + task := &models.Task{} + id := mux.Vars(r)["id"] + err := db.QueryRow(`SELECT name, content, created_at FROM tasks WHERE id = $1 AND user_id = $2`, id, decoded.UserId).Scan(&task.Name, &task.Content, &task.CreatedAt) + if err != nil { + u.Respond(w, http.StatusNotFound, "Failure", err.Error(), nil) + return + } + + u.Respond(w, http.StatusOK, "Success", "Success", map[string]interface{}{ + "name": task.Name, + "content": task.Content, + "created_at": task.CreatedAt, + }) +} var Add = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { - // decode userid from jwt => userId + decoded := r.Context().Value("user").(*models.Token) task := &models.Task{ - CreatedAt: time.Now().UTC(), - UserId: 1, + UserId: decoded.UserId, } err := json.NewDecoder(r.Body).Decode(task) if err != nil { @@ -55,7 +63,7 @@ var Add = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { return } // insert database - err = db.QueryRow(`INSERT INTO tasks(name, content, created_at, user_id) VALUES($1, $2, $3, $4, $5) RETURNING name, email`, task.Name, task.Content, task.CreatedAt, task.UserId).Scan(&task.Name, &task.Content) + err = db.QueryRow(`INSERT INTO tasks(name, content, user_id) VALUES($3, $2, $1) RETURNING name, content, created_at`, task.UserId, task.Content, task.Name).Scan(&task.Name, &task.Content, &task.CreatedAt) if err != nil { u.Respond(w, http.StatusBadRequest, "Failure", err.Error(), nil) return @@ -65,25 +73,25 @@ var Add = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { "content": task.Content, "created_at": task.CreatedAt, }) - - // check task number today greater than or equal to current task limitDayTasks - // if true - // u.Respond(w, http.StatusNotAcceptable, map[string]interface{}{}) - // // else - // // update task number today += 1 - // u.Respond(w, http.StatusCreated, map[string]interface{}{}) } -// var Edit = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { -// id := r.URL.Query().Get("id") -// err := db.QueryRow(`UPDATE tasks SET name = $1 WHERE id = 3; RETURNING name, email`, id).Scan(&task.Name, &task.Content) -// if err != nil { -// u.Respond(w, http.StatusNotFound, "Failure", err.Error(), nil) -// return -// } -// check taskID exist -// if not exists -// u.Respond(w, http.StatusNotFound, map[string]interface{}{}) -// // else -// u.Respond(w, http.StatusOK, map[string]interface{}{}) -// } +var Edit = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { + decoded := r.Context().Value("user").(*models.Token) + task := &models.Task{} + id := mux.Vars(r)["id"] + err := json.NewDecoder(r.Body).Decode(task) + if err != nil { + u.Respond(w, http.StatusBadRequest, "Failure", err.Error(), nil) + return + } + err = db.QueryRow(`UPDATE tasks SET name = $3 WHERE id = $1 AND user_id = $2 RETURNING name, content, created_at`, id, decoded.UserId, task.Name).Scan(&task.Name, &task.Content, &task.CreatedAt) + if err != nil { + u.Respond(w, http.StatusBadRequest, "Failure", err.Error(), nil) + return + } + u.Respond(w, http.StatusCreated, "Success", "Success creates task", map[string]interface{}{ + "name": task.Name, + "content": task.Content, + "created_at": task.CreatedAt, + }) +} diff --git a/controllers/user.go b/controllers/user.go index fecf919f8..8373a1496 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -37,9 +37,9 @@ var SignUp = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { u.Respond(w, http.StatusBadRequest, "Failure", err.Error(), nil) return } - fmt.Println(user) + // send token jwt here - tk := &models.Token{UserId: user.ID} + tk := &models.Token{UserId: user.ID, LimitDayTasks: user.LimitDayTasks} token := jwt.NewWithClaims(jwt.GetSigningMethod("HS256"), tk) tokenString, _ := token.SignedString([]byte(os.Getenv("SECRET_TOKEN"))) // response and send token to client @@ -62,7 +62,7 @@ var Login = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { email string password string ) - err = db.QueryRow(`SELECT id, name, email, password FROM users WHERE email = $1`, user.Email).Scan(&user.ID, &user.Name, &email, &password) + err = db.QueryRow(`SELECT id, name, email, password, limit_day_tasks FROM users WHERE email = $1`, user.Email).Scan(&user.ID, &user.Name, &email, &password, &user.LimitDayTasks) if err != nil { u.Respond(w, http.StatusNotFound, "Failure", "Your email invalid", nil) return @@ -75,7 +75,10 @@ var Login = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { // if email and password OK //Create JWT token fmt.Println(user) - tk := &models.Token{UserId: user.ID} + tk := &models.Token{ + UserId: user.ID, + LimitDayTasks: user.LimitDayTasks, + } token := jwt.NewWithClaims(jwt.GetSigningMethod("HS256"), tk) fmt.Println(os.Getenv("SECRET_TOKEN")) tokenString, _ := token.SignedString([]byte(os.Getenv("SECRET_TOKEN"))) @@ -88,13 +91,10 @@ var Login = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { } var GetMe = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { - var userID = r.Context().Value("user").(uint32) - - var user = &models.User{ - ID: userID, - } - err := db.QueryRow(`SELECT name, email, is_payment, limit_day_tasks FROM users WHERE id = $1`, user.ID).Scan(&user.Name, &user.Email, &user.IsPayment, &user.LimitDayTasks) + decoded := r.Context().Value("user").(*models.Token) + user := &models.User{} + err := db.QueryRow(`SELECT name, email, is_payment, limit_day_tasks FROM users WHERE id = $1`, decoded.UserId).Scan(&user.Name, &user.Email, &user.IsPayment, &user.LimitDayTasks) if err != nil { u.Respond(w, http.StatusNotFound, "Failure", err.Error(), nil) @@ -109,8 +109,20 @@ var GetMe = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { } var UpdateMe = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { + decoded := r.Context().Value("user").(*models.Token) + user := models.User{} - // success + err := json.NewDecoder(r.Body).Decode(user) + if err != nil { + u.Respond(w, http.StatusBadRequest, "Failure", err.Error(), nil) + return + } + err = db.QueryRow(`UPDATE tasks SET name = $2 WHERE id = $1 RETURNING name, email`, decoded.UserId, user.Name).Scan(&user.Name, &user.Email) + if err != nil { + u.Respond(w, http.StatusNotFound, "Failure", err.Error(), nil) + return + } + u.Respond(w, http.StatusOK, "Success", "Success", map[string]interface{}{ "name": user.Name, "email": user.Email, diff --git a/models/task.go b/models/task.go index 84fad46e2..f4543ce5e 100644 --- a/models/task.go +++ b/models/task.go @@ -7,5 +7,5 @@ type Task struct { Name string `json:"name" validate:"required"` Content string `json:"content"` CreatedAt time.Time `json:"createdAt"` - UserId int32 `json:"userId"` + UserId uint32 `json:"userId"` } diff --git a/models/user.go b/models/user.go index 8529146ae..c12b3b44e 100644 --- a/models/user.go +++ b/models/user.go @@ -5,7 +5,8 @@ import ( ) type Token struct { - UserId uint32 + UserId uint32 + LimitDayTasks uint jwt.StandardClaims } type User struct { From 7d3aa122bda990e2c355e93c6c0075bbebf7bbba Mon Sep 17 00:00:00 2001 From: Quang Ha Date: Fri, 24 Jun 2022 14:08:58 +0700 Subject: [PATCH 08/41] commplete logic code --- app/app.go | 13 +++++++++++++ controllers/payment.go | 30 ++++++++++++++++++++++++++++++ controllers/task.go | 42 +++++++++++++++++++++++++++++++++++------- controllers/user.go | 36 +++++++++++++++++++++--------------- models/user.go | 1 - 5 files changed, 99 insertions(+), 23 deletions(-) create mode 100644 controllers/payment.go diff --git a/app/app.go b/app/app.go index 3daaab944..bf8783400 100644 --- a/app/app.go +++ b/app/app.go @@ -52,12 +52,17 @@ func (a *App) Routes() { taskRouter.HandleFunc("/{id}", a.GetTask).Methods("GET") taskRouter.HandleFunc("/add", a.Add).Methods("POST") taskRouter.HandleFunc("/{id}", a.Edit).Methods("PATCH") + taskRouter.HandleFunc("/{id}", a.Delete).Methods("DELETE") + // sub routes like http://:/api/payments + paymentRouter := router.PathPrefix("/api/payments").Subrouter() + paymentRouter.HandleFunc("", a.Payment).Methods("POST") // runs database router.Use(mux.CORSMethodMiddleware(router)) router.Use(controllers.JwtAuthentication) } func (a *App) GetMe(w http.ResponseWriter, r *http.Request) { + c.GetTasks(a.DB, w, r) c.GetMe(a.DB, w, r) } @@ -89,6 +94,14 @@ func (a *App) Edit(w http.ResponseWriter, r *http.Request) { c.Edit(a.DB, w, r) } +func (a *App) Delete(w http.ResponseWriter, r *http.Request) { + c.Delete(a.DB, w, r) +} + +func (a *App) Payment(w http.ResponseWriter, r *http.Request) { + c.Payment(a.DB, w, r) +} + func (a *App) Run(host string) { log.Fatal(http.ListenAndServe(host, a.Router)) } diff --git a/controllers/payment.go b/controllers/payment.go new file mode 100644 index 000000000..2798cc191 --- /dev/null +++ b/controllers/payment.go @@ -0,0 +1,30 @@ +package controllers + +import ( + "database/sql" + "net/http" + + "github.com/manabie-com/togo/models" + u "github.com/manabie-com/togo/utils" +) + +func Payment(db *sql.DB, w http.ResponseWriter, r *http.Request) { + decoded := r.Context().Value("user").(*models.Token) + user := &models.User{ + LimitDayTasks: 20, + IsPayment: true, + } + + err := db.QueryRow(`UPDATE users SET is_payment = $1, limit_day_tasks = $2 WHERE id = $3 RETURNING name, email, limit_day_tasks`, user.IsPayment, user.LimitDayTasks, decoded.UserId).Scan(&user.Name, &user.Email, &user.LimitDayTasks) + if err != nil { + u.Respond(w, http.StatusBadRequest, "Failure", "Something went wrong. Please try again", nil) + return + } + + u.Respond(w, http.StatusOK, "Success", "Success upgrade Premium account. Please login again to try new upgrade", map[string]interface{}{ + "name": user.Name, + "email": user.Email, + "is_payment": user.IsPayment, + "limit_day_tasks": user.LimitDayTasks, + }) +} diff --git a/controllers/task.go b/controllers/task.go index 8d1fb7486..a306ec3a7 100644 --- a/controllers/task.go +++ b/controllers/task.go @@ -14,25 +14,30 @@ import ( var GetTasks = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { // get user id here decoded := r.Context().Value("user").(*models.Token) - rows, err := db.Query(`SELECT name, content, created_at FROM tasks WHERE user_id = $1`, decoded.UserId) + rows, err := db.Query(`SELECT * FROM tasks WHERE user_id = $1`, decoded.UserId) if err != nil { u.Respond(w, http.StatusNotFound, "Failure", err.Error(), nil) return } + var tasks []*models.Task for rows.Next() { var task = &models.Task{} - rows.Scan(&task.Name, &task.Content, &task.CreatedAt) + rows.Scan(&task.ID, &task.Name, &task.Content, &task.CreatedAt, &task.UserId) tasks = append(tasks, task) } + u.Respond(w, http.StatusOK, "Success", "Success", tasks) } var GetTask = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { + // decode token from middleware decoded := r.Context().Value("user").(*models.Token) task := &models.Task{} - id := mux.Vars(r)["id"] + + id := mux.Vars(r)["id"] // get id from url param + err := db.QueryRow(`SELECT name, content, created_at FROM tasks WHERE id = $1 AND user_id = $2`, id, decoded.UserId).Scan(&task.Name, &task.Content, &task.CreatedAt) if err != nil { u.Respond(w, http.StatusNotFound, "Failure", err.Error(), nil) @@ -47,28 +52,39 @@ var GetTask = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { } var Add = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { + // decode token from middleware decoded := r.Context().Value("user").(*models.Token) task := &models.Task{ UserId: decoded.UserId, } + // json body -> task object err := json.NewDecoder(r.Body).Decode(task) if err != nil { u.Respond(w, http.StatusBadRequest, "Failure", "invalid request", nil) return } + // validate task object validate := validator.New() - if err = validate.Struct(task); err != nil { u.Respond(w, http.StatusBadRequest, "Failure", err.Error(), nil) return } + // check limit day tasks + var recordsLength uint + _ = db.QueryRow(`SELECT COUNT(id) + FROM tasks + WHERE created_at >= NOW() - INTERVAL '24 HOURS' AND user_id = $1`, decoded.UserId).Scan(&recordsLength) + if recordsLength == decoded.LimitDayTasks { + u.Respond(w, http.StatusBadRequest, "Failure", "Today tasks had limited, Please Comeback tomorrow.", nil) + return + } // insert database err = db.QueryRow(`INSERT INTO tasks(name, content, user_id) VALUES($3, $2, $1) RETURNING name, content, created_at`, task.UserId, task.Content, task.Name).Scan(&task.Name, &task.Content, &task.CreatedAt) if err != nil { u.Respond(w, http.StatusBadRequest, "Failure", err.Error(), nil) return } - u.Respond(w, http.StatusCreated, "Success", "Success creates task", map[string]interface{}{ + u.Respond(w, http.StatusCreated, "Success", "Success create task", map[string]interface{}{ "name": task.Name, "content": task.Content, "created_at": task.CreatedAt, @@ -78,7 +94,7 @@ var Add = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { var Edit = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { decoded := r.Context().Value("user").(*models.Token) task := &models.Task{} - id := mux.Vars(r)["id"] + id := mux.Vars(r)["id"] // get id from url params err := json.NewDecoder(r.Body).Decode(task) if err != nil { u.Respond(w, http.StatusBadRequest, "Failure", err.Error(), nil) @@ -89,9 +105,21 @@ var Edit = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { u.Respond(w, http.StatusBadRequest, "Failure", err.Error(), nil) return } - u.Respond(w, http.StatusCreated, "Success", "Success creates task", map[string]interface{}{ + + u.Respond(w, http.StatusCreated, "Success", "Success create task", map[string]interface{}{ "name": task.Name, "content": task.Content, "created_at": task.CreatedAt, }) } + +var Delete = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { + decoded := r.Context().Value("user").(*models.Token) + id := mux.Vars(r)["id"] + _, err := db.Exec(`DELETE FROM tasks WHERE id = $1 AND user_id = $2`, id, decoded.UserId) + if err != nil { + u.Respond(w, http.StatusNotFound, "Failure", err.Error(), nil) + return + } + u.Respond(w, http.StatusCreated, "Success", "Success delete task", nil) +} diff --git a/controllers/user.go b/controllers/user.go index 8373a1496..688385f79 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -3,7 +3,6 @@ package controllers import ( "database/sql" "encoding/json" - "fmt" "net/http" "os" @@ -21,20 +20,20 @@ var SignUp = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { // validate err := json.NewDecoder(r.Body).Decode(user) if err != nil { - u.Respond(w, http.StatusBadRequest, "Failure", err.Error(), nil) + u.Respond(w, http.StatusBadRequest, "Failure", "Invalid input format: "+err.Error(), nil) return } validate := validator.New() if err = validate.Struct(user); err != nil { - u.Respond(w, http.StatusBadRequest, "Failure", err.Error(), nil) + u.Respond(w, http.StatusBadRequest, "Failure", "Invalid input field: "+err.Error(), nil) return } // insert database err = db.QueryRow(`INSERT INTO users(name, email, password) VALUES($1, $2, $3) RETURNING id, name, email`, user.Name, user.Email, user.Password).Scan(&user.ID, &user.Name, &user.Email) if err != nil { - u.Respond(w, http.StatusBadRequest, "Failure", err.Error(), nil) + u.Respond(w, http.StatusServiceUnavailable, "Failure", "OOPS, Something went wrong, please try again later", nil) return } @@ -55,7 +54,7 @@ var Login = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { // validate err := json.NewDecoder(r.Body).Decode(user) if err != nil { - u.Respond(w, http.StatusBadRequest, "Failure", err.Error(), nil) + u.Respond(w, http.StatusBadRequest, "Failure", "Invalid input format: "+err.Error(), nil) return } var ( @@ -74,13 +73,11 @@ var Login = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { } // if email and password OK //Create JWT token - fmt.Println(user) tk := &models.Token{ UserId: user.ID, LimitDayTasks: user.LimitDayTasks, } token := jwt.NewWithClaims(jwt.GetSigningMethod("HS256"), tk) - fmt.Println(os.Getenv("SECRET_TOKEN")) tokenString, _ := token.SignedString([]byte(os.Getenv("SECRET_TOKEN"))) // response and send token to client u.Respond(w, http.StatusOK, "Success", "Login Success", map[string]interface{}{ @@ -91,13 +88,14 @@ var Login = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { } var GetMe = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { - + // get decoded token from middleware decoded := r.Context().Value("user").(*models.Token) user := &models.User{} + // query err := db.QueryRow(`SELECT name, email, is_payment, limit_day_tasks FROM users WHERE id = $1`, decoded.UserId).Scan(&user.Name, &user.Email, &user.IsPayment, &user.LimitDayTasks) if err != nil { - u.Respond(w, http.StatusNotFound, "Failure", err.Error(), nil) + u.Respond(w, http.StatusBadRequest, "Failure", "Something went wrong when collect your account. Please try again", nil) return } u.Respond(w, http.StatusOK, "Success", "Success", map[string]interface{}{ @@ -109,21 +107,29 @@ var GetMe = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { } var UpdateMe = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { + // get decoded token from middleware decoded := r.Context().Value("user").(*models.Token) - - user := models.User{} + user := &models.User{} + // convert json -> user object err := json.NewDecoder(r.Body).Decode(user) if err != nil { - u.Respond(w, http.StatusBadRequest, "Failure", err.Error(), nil) + u.Respond(w, http.StatusBadRequest, "Failure", "Invalid input format: "+err.Error(), nil) return } - err = db.QueryRow(`UPDATE tasks SET name = $2 WHERE id = $1 RETURNING name, email`, decoded.UserId, user.Name).Scan(&user.Name, &user.Email) + // get user info + err = db.QueryRow(`SELECT name, email FROM users WHERE user_id = $1`, decoded.UserId).Scan(&user.Name, &user.Email) if err != nil { - u.Respond(w, http.StatusNotFound, "Failure", err.Error(), nil) + u.Respond(w, http.StatusBadRequest, "Failure", "Something went wrong when collect your account. Please try again", nil) + return + } + // update me + err = db.QueryRow(`UPDATE users, email SET name = $1, email = $2 WHERE id = $3 RETURNING name, email`, user.Name, user.Email, decoded.UserId).Scan(&user.Name, &user.Email) + if err != nil { + u.Respond(w, http.StatusBadRequest, "Failure", "Something went wrong when update your account.. Please try again", nil) return } - u.Respond(w, http.StatusOK, "Success", "Success", map[string]interface{}{ + u.Respond(w, http.StatusOK, "Success", "Success update your account!", map[string]interface{}{ "name": user.Name, "email": user.Email, "is_payment": user.IsPayment, diff --git a/models/user.go b/models/user.go index c12b3b44e..7a9d58548 100644 --- a/models/user.go +++ b/models/user.go @@ -16,5 +16,4 @@ type User struct { Password string `json:"password" validate:"required,min=6,max=20"` IsPayment bool `json:"isPayment" validate:"omitempty"` LimitDayTasks uint `json:"limitDayTasks" validate:"omitempty"` - Task *[]Task } From 166f61ccfac0bfaf7b386e83d322f0147ea0a589 Mon Sep 17 00:00:00 2001 From: Quang Ha Date: Fri, 24 Jun 2022 14:46:13 +0700 Subject: [PATCH 09/41] make it better --- go.mod | 10 +++++++--- go.sum | 13 +++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8bdefe153..9335e7623 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,6 @@ go 1.18 require ( github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect - github.com/go-pg/pg/v10 v10.10.6 // indirect github.com/go-pg/zerochecker v0.2.0 // indirect github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect @@ -13,16 +12,21 @@ require ( github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/joho/godotenv v1.4.0 // indirect + github.com/kr/fs v0.1.0 // indirect + github.com/kr/pretty v0.3.0 // indirect + github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/lib/pq v1.10.6 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rogpeppe/go-internal v1.8.1 // indirect github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect github.com/vmihailenco/bufpool v0.1.11 // indirect github.com/vmihailenco/msgpack/v5 v5.3.4 // indirect github.com/vmihailenco/tagparser v0.1.2 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect - golang.org/x/sys v0.0.0-20210923061019-b8560ed6a9b7 // indirect + golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664 // indirect golang.org/x/text v0.3.7 // indirect - gorm.io/gorm v1.23.6 // indirect + golang.org/x/tools v0.1.11 // indirect mellium.im/sasl v0.2.1 // indirect ) diff --git a/go.sum b/go.sum index d27901f4d..8f2e97e36 100644 --- a/go.sum +++ b/go.sum @@ -50,11 +50,15 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= @@ -69,16 +73,21 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/tools/godep v0.0.0-20180126220526-ce0bfadeb516 h1:h4a8ZFxjlRVGsFGP4l/AdnoUYcF3pfxzyepS3oKZ8mE= +github.com/tools/godep v0.0.0-20180126220526-ce0bfadeb516/go.mod h1:OGh2HQGYVW+2+ZdB+DgJhI75kivkKWtVcIxI/pesDsY= github.com/vmihailenco/bufpool v0.1.11 h1:gOq2WmBrq0i2yW5QJ16ykccQ4wH9UyEsgLm6czKAd94= github.com/vmihailenco/bufpool v0.1.11/go.mod h1:AFf/MOy3l2CFTKbxwt0mp2MwnqjNEs5H/UxrkA5jxTQ= github.com/vmihailenco/msgpack/v5 v5.3.4 h1:qMKAwOV+meBw2Y8k9cVwAy7qErtYCwBzZ2ellBfvnqc= @@ -128,6 +137,8 @@ golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 h1:siQdpVirKtzPhKl3lZWozZraC golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210923061019-b8560ed6a9b7 h1:c20P3CcPbopVp2f7099WLOqSNKURf30Z0uq66HpijZY= golang.org/x/sys v0.0.0-20210923061019-b8560ed6a9b7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664 h1:wEZYwx+kK+KlZ0hpvP2Ls1Xr4+RWnlzGFwPP0aiDjIU= +golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -140,6 +151,8 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.1.11 h1:loJ25fNOEhSXfHrpoGj91eCUThwdNX6u24rO1xnNteY= +golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= From 53ff17b7e36db54a2addf034f080ea35a263d4de Mon Sep 17 00:00:00 2001 From: Quang Ha Date: Fri, 24 Jun 2022 16:49:31 +0700 Subject: [PATCH 10/41] implement DeleteMe function and fix bugss --- app/app.go | 6 +++- controllers/task.go | 2 +- controllers/user.go | 82 +++++++++++++++++++++++++++++++++++++++------ models/user.go | 1 + 4 files changed, 79 insertions(+), 12 deletions(-) diff --git a/app/app.go b/app/app.go index bf8783400..a3c8f6f33 100644 --- a/app/app.go +++ b/app/app.go @@ -46,6 +46,7 @@ func (a *App) Routes() { userRouter.HandleFunc("/signup", a.SignUp).Methods("POST") userRouter.HandleFunc("/login", a.Login).Methods("POST") userRouter.HandleFunc("/edit", a.UpdateMe).Methods("PATCH") + userRouter.HandleFunc("/delete", a.DeleteMe).Methods("DELETE") // sub router like http://:/api/tasks taskRouter := router.PathPrefix("/api/tasks").Subrouter() taskRouter.HandleFunc("", a.GetTasks).Methods("GET") @@ -62,7 +63,6 @@ func (a *App) Routes() { } func (a *App) GetMe(w http.ResponseWriter, r *http.Request) { - c.GetTasks(a.DB, w, r) c.GetMe(a.DB, w, r) } @@ -78,6 +78,10 @@ func (a *App) UpdateMe(w http.ResponseWriter, r *http.Request) { c.UpdateMe(a.DB, w, r) } +func (a *App) DeleteMe(w http.ResponseWriter, r *http.Request) { + c.DeleteMe(a.DB, w, r) +} + func (a *App) GetTasks(w http.ResponseWriter, r *http.Request) { c.GetTasks(a.DB, w, r) } diff --git a/controllers/task.go b/controllers/task.go index a306ec3a7..9ba35f8ea 100644 --- a/controllers/task.go +++ b/controllers/task.go @@ -40,7 +40,7 @@ var GetTask = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { err := db.QueryRow(`SELECT name, content, created_at FROM tasks WHERE id = $1 AND user_id = $2`, id, decoded.UserId).Scan(&task.Name, &task.Content, &task.CreatedAt) if err != nil { - u.Respond(w, http.StatusNotFound, "Failure", err.Error(), nil) + u.Respond(w, http.StatusNotFound, "Failure", "Not found task", nil) return } diff --git a/controllers/user.go b/controllers/user.go index 688385f79..019ade31f 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -33,7 +33,7 @@ var SignUp = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { // insert database err = db.QueryRow(`INSERT INTO users(name, email, password) VALUES($1, $2, $3) RETURNING id, name, email`, user.Name, user.Email, user.Password).Scan(&user.ID, &user.Name, &user.Email) if err != nil { - u.Respond(w, http.StatusServiceUnavailable, "Failure", "OOPS, Something went wrong, please try again later", nil) + u.Respond(w, http.StatusBadRequest, "Failure", "Maybe your email is duplicated, Please try again", nil) return } @@ -61,16 +61,23 @@ var Login = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { email string password string ) - err = db.QueryRow(`SELECT id, name, email, password, limit_day_tasks FROM users WHERE email = $1`, user.Email).Scan(&user.ID, &user.Name, &email, &password, &user.LimitDayTasks) + err = db.QueryRow(`SELECT id, name, email, password, limit_day_tasks, is_active FROM users WHERE email = $1`, user.Email).Scan(&user.ID, &user.Name, &email, &password, &user.LimitDayTasks, &user.IsActive) if err != nil { u.Respond(w, http.StatusNotFound, "Failure", "Your email invalid", nil) return } + // if email exist and password incorrect if email != user.Email || password != user.Password { u.Respond(w, http.StatusUnauthorized, "Failure", "Password incorrect", nil) return } + // create message case 1: Active user, case 2: unActive user + var message = "Login Success" + if !user.IsActive { + message = "Welcome back" + _, _ = db.Exec(`UPDATE users SET is_active = $1 WHERE id = $2`, true, user.ID) + } // if email and password OK //Create JWT token tk := &models.Token{ @@ -80,7 +87,7 @@ var Login = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { token := jwt.NewWithClaims(jwt.GetSigningMethod("HS256"), tk) tokenString, _ := token.SignedString([]byte(os.Getenv("SECRET_TOKEN"))) // response and send token to client - u.Respond(w, http.StatusOK, "Success", "Login Success", map[string]interface{}{ + u.Respond(w, http.StatusOK, "Success", message, map[string]interface{}{ "name": user.Name, "email": user.Email, "token": tokenString, @@ -92,7 +99,7 @@ var GetMe = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { decoded := r.Context().Value("user").(*models.Token) user := &models.User{} // query - err := db.QueryRow(`SELECT name, email, is_payment, limit_day_tasks FROM users WHERE id = $1`, decoded.UserId).Scan(&user.Name, &user.Email, &user.IsPayment, &user.LimitDayTasks) + err := db.QueryRow(`SELECT name, email, is_payment, limit_day_tasks, is_active FROM users WHERE id = $1`, decoded.UserId).Scan(&user.Name, &user.Email, &user.IsPayment, &user.LimitDayTasks, &user.IsActive) if err != nil { u.Respond(w, http.StatusBadRequest, "Failure", "Something went wrong when collect your account. Please try again", nil) @@ -116,23 +123,78 @@ var UpdateMe = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { u.Respond(w, http.StatusBadRequest, "Failure", "Invalid input format: "+err.Error(), nil) return } + // get user info - err = db.QueryRow(`SELECT name, email FROM users WHERE user_id = $1`, decoded.UserId).Scan(&user.Name, &user.Email) + var ( + name string + email string + password string + ) + err = db.QueryRow(`SELECT name, email, password FROM users WHERE id = $1`, decoded.UserId).Scan(&name, &email, &password) if err != nil { u.Respond(w, http.StatusBadRequest, "Failure", "Something went wrong when collect your account. Please try again", nil) return } + // confirm password + if password != user.Password { + u.Respond(w, http.StatusUnauthorized, "Failure", "Password incorrect. Please try again", nil) + return + } + + validate := validator.New() + if err := validate.Var(user.Email, "email"); user.Email != "" && err != nil { + u.Respond(w, http.StatusBadRequest, "Failure", "Invalid input email: "+err.Error(), nil) + return + } + + if err := validate.Var(user.Name, "min=5,max=20"); user.Name != "" && err != nil { + u.Respond(w, http.StatusBadRequest, "Failure", "Invalid input name: "+err.Error(), nil) + return + } // update me - err = db.QueryRow(`UPDATE users, email SET name = $1, email = $2 WHERE id = $3 RETURNING name, email`, user.Name, user.Email, decoded.UserId).Scan(&user.Name, &user.Email) + _, err = db.Exec(`UPDATE users SET name = $1, email = $2 WHERE id = $3`, user.Name, user.Email, decoded.UserId) if err != nil { u.Respond(w, http.StatusBadRequest, "Failure", "Something went wrong when update your account.. Please try again", nil) return } u.Respond(w, http.StatusOK, "Success", "Success update your account!", map[string]interface{}{ - "name": user.Name, - "email": user.Email, - "is_payment": user.IsPayment, - "limit_day_tasks": user.LimitDayTasks, + "name": user.Name, + "email": user.Email, + "is_payment": user.IsPayment, }) } + +var DeleteMe = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { + // get decoded token from middleware + decoded := r.Context().Value("user").(*models.Token) + user := &models.User{ + IsActive: false, + } + // convert json -> user object + err := json.NewDecoder(r.Body).Decode(user) + if err != nil { + u.Respond(w, http.StatusBadRequest, "Failure", "Invalid input format: "+err.Error(), nil) + return + } + // get user info + var password string + err = db.QueryRow(`SELECT name, email, password FROM users WHERE id = $1`, decoded.UserId).Scan(&user.Name, &user.Email, &password) + if err != nil { + u.Respond(w, http.StatusBadRequest, "Failure", "Something went wrong when collect your account. Please try again", nil) + return + } + // confirm password + if password != user.Password { + u.Respond(w, http.StatusUnauthorized, "Failure", "Password incorrect. Please try again", nil) + return + } + // update me + _, err = db.Exec(`UPDATE users SET is_active = $1 WHERE id = $2`, user.IsActive, decoded.UserId) + if err != nil { + u.Respond(w, http.StatusBadRequest, "Failure", "Something went wrong when delete your account. Please try again", nil) + return + } + + u.Respond(w, http.StatusOK, "Success", "Success delete your account!", nil) +} diff --git a/models/user.go b/models/user.go index 7a9d58548..916068df3 100644 --- a/models/user.go +++ b/models/user.go @@ -15,5 +15,6 @@ type User struct { Name string `json:"name" validate:"required,min=5,max=20"` Password string `json:"password" validate:"required,min=6,max=20"` IsPayment bool `json:"isPayment" validate:"omitempty"` + IsActive bool `json:"isActive"` LimitDayTasks uint `json:"limitDayTasks" validate:"omitempty"` } From 227041ed12ba275b8f1ef2680e13612121230908 Mon Sep 17 00:00:00 2001 From: Quang Ha Date: Sun, 26 Jun 2022 00:40:57 +0700 Subject: [PATCH 11/41] structure folder models vs controllers --- .vscode/settings.json | 5 +++ controllers/task.go | 28 ++++++++++---- controllers/user.go | 90 ++++++++++++++++++++----------------------- models/task.go | 23 ++++++++++- models/token.go | 19 +++++++++ models/user.go | 31 ++++++++++++--- 6 files changed, 132 insertions(+), 64 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 models/token.go diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..a2208ca38 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "cSpell.words": [ + "manabie" + ] +} \ No newline at end of file diff --git a/controllers/task.go b/controllers/task.go index 9ba35f8ea..d71678fa7 100644 --- a/controllers/task.go +++ b/controllers/task.go @@ -4,6 +4,7 @@ import ( "database/sql" "encoding/json" "net/http" + "strconv" "github.com/go-playground/validator/v10" "github.com/gorilla/mux" @@ -27,19 +28,28 @@ var GetTasks = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { rows.Scan(&task.ID, &task.Name, &task.Content, &task.CreatedAt, &task.UserId) tasks = append(tasks, task) } - + //Everything OK u.Respond(w, http.StatusOK, "Success", "Success", tasks) } var GetTask = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { // decode token from middleware decoded := r.Context().Value("user").(*models.Token) - task := &models.Task{} + // convert id params string -> uint32 + id := func(id string) uint32 { + u64, err := strconv.ParseUint(id, 10, 32) + if err != nil { + u.Respond(w, http.StatusBadRequest, "Failure", "ID must be a number type.", nil) + } + return uint32(u64) + }(mux.Vars(r)["id"]) - id := mux.Vars(r)["id"] // get id from url param + task := &models.Task{ + ID: id, + UserId: decoded.UserId, + } - err := db.QueryRow(`SELECT name, content, created_at FROM tasks WHERE id = $1 AND user_id = $2`, id, decoded.UserId).Scan(&task.Name, &task.Content, &task.CreatedAt) - if err != nil { + if err := task.GetOneByUserId(db); err != nil { u.Respond(w, http.StatusNotFound, "Failure", "Not found task", nil) return } @@ -79,11 +89,11 @@ var Add = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { return } // insert database - err = db.QueryRow(`INSERT INTO tasks(name, content, user_id) VALUES($3, $2, $1) RETURNING name, content, created_at`, task.UserId, task.Content, task.Name).Scan(&task.Name, &task.Content, &task.CreatedAt) - if err != nil { + if task.InsertOne(db); err != nil { u.Respond(w, http.StatusBadRequest, "Failure", err.Error(), nil) return } + //everything OK u.Respond(w, http.StatusCreated, "Success", "Success create task", map[string]interface{}{ "name": task.Name, "content": task.Content, @@ -93,8 +103,10 @@ var Add = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { var Edit = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { decoded := r.Context().Value("user").(*models.Token) - task := &models.Task{} id := mux.Vars(r)["id"] // get id from url params + task := &models.Task{ + UserId: decoded.UserId, + } err := json.NewDecoder(r.Body).Decode(task) if err != nil { u.Respond(w, http.StatusBadRequest, "Failure", err.Error(), nil) diff --git a/controllers/user.go b/controllers/user.go index 019ade31f..5e725cc0e 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -4,9 +4,7 @@ import ( "database/sql" "encoding/json" "net/http" - "os" - "github.com/dgrijalva/jwt-go" "github.com/go-playground/validator/v10" "github.com/manabie-com/togo/models" u "github.com/manabie-com/togo/utils" @@ -17,31 +15,26 @@ var SignUp = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { IsPayment: false, LimitDayTasks: 10, } - // validate - err := json.NewDecoder(r.Body).Decode(user) - if err != nil { + // decode json body to user + if err := json.NewDecoder(r.Body).Decode(user); err != nil { u.Respond(w, http.StatusBadRequest, "Failure", "Invalid input format: "+err.Error(), nil) return } - + // validate user object validate := validator.New() - - if err = validate.Struct(user); err != nil { + if err := validate.Struct(user); err != nil { u.Respond(w, http.StatusBadRequest, "Failure", "Invalid input field: "+err.Error(), nil) return } // insert database - err = db.QueryRow(`INSERT INTO users(name, email, password) VALUES($1, $2, $3) RETURNING id, name, email`, user.Name, user.Email, user.Password).Scan(&user.ID, &user.Name, &user.Email) - if err != nil { - u.Respond(w, http.StatusBadRequest, "Failure", "Maybe your email is duplicated, Please try again", nil) + if err := user.InsertOne(db); err != nil { + u.Respond(w, http.StatusBadRequest, "Failure", "Your email is duplicated, Please try again", nil) return } - // send token jwt here tk := &models.Token{UserId: user.ID, LimitDayTasks: user.LimitDayTasks} - token := jwt.NewWithClaims(jwt.GetSigningMethod("HS256"), tk) - tokenString, _ := token.SignedString([]byte(os.Getenv("SECRET_TOKEN"))) - // response and send token to client + tokenString := tk.CreateToken() + // everything Ok u.Respond(w, http.StatusCreated, "Success", "Created Account", map[string]interface{}{ "name": user.Name, "email": user.Email, @@ -57,16 +50,16 @@ var Login = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { u.Respond(w, http.StatusBadRequest, "Failure", "Invalid input format: "+err.Error(), nil) return } + var ( - email string - password string + email string = user.Email + password string = user.Password ) - err = db.QueryRow(`SELECT id, name, email, password, limit_day_tasks, is_active FROM users WHERE email = $1`, user.Email).Scan(&user.ID, &user.Name, &email, &password, &user.LimitDayTasks, &user.IsActive) - if err != nil { - u.Respond(w, http.StatusNotFound, "Failure", "Your email invalid", nil) + // get user by email + if err := user.GetOneByEmail(db); err != nil { + u.Respond(w, http.StatusBadRequest, "Failure", "Not found account with your email, Please provide a valid email address!", nil) return } - // if email exist and password incorrect if email != user.Email || password != user.Password { u.Respond(w, http.StatusUnauthorized, "Failure", "Password incorrect", nil) @@ -84,9 +77,8 @@ var Login = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { UserId: user.ID, LimitDayTasks: user.LimitDayTasks, } - token := jwt.NewWithClaims(jwt.GetSigningMethod("HS256"), tk) - tokenString, _ := token.SignedString([]byte(os.Getenv("SECRET_TOKEN"))) - // response and send token to client + tokenString := tk.CreateToken() + // everything Ok u.Respond(w, http.StatusOK, "Success", message, map[string]interface{}{ "name": user.Name, "email": user.Email, @@ -97,14 +89,15 @@ var Login = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { var GetMe = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { // get decoded token from middleware decoded := r.Context().Value("user").(*models.Token) - user := &models.User{} + user := &models.User{ + ID: decoded.UserId, + } // query - err := db.QueryRow(`SELECT name, email, is_payment, limit_day_tasks, is_active FROM users WHERE id = $1`, decoded.UserId).Scan(&user.Name, &user.Email, &user.IsPayment, &user.LimitDayTasks, &user.IsActive) - - if err != nil { + if err := user.GetOneById(db); err != nil { u.Respond(w, http.StatusBadRequest, "Failure", "Something went wrong when collect your account. Please try again", nil) return } + // everything Ok u.Respond(w, http.StatusOK, "Success", "Success", map[string]interface{}{ "name": user.Name, "email": user.Email, @@ -116,22 +109,23 @@ var GetMe = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { var UpdateMe = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { // get decoded token from middleware decoded := r.Context().Value("user").(*models.Token) - user := &models.User{} + user := &models.User{ + ID: decoded.UserId, + } // convert json -> user object err := json.NewDecoder(r.Body).Decode(user) if err != nil { u.Respond(w, http.StatusBadRequest, "Failure", "Invalid input format: "+err.Error(), nil) return } - // get user info var ( - name string - email string - password string + name string = user.Name + email string = user.Email + password string = user.Password ) - err = db.QueryRow(`SELECT name, email, password FROM users WHERE id = $1`, decoded.UserId).Scan(&name, &email, &password) - if err != nil { + + if err := user.GetOneById(db); err != nil { u.Respond(w, http.StatusBadRequest, "Failure", "Something went wrong when collect your account. Please try again", nil) return } @@ -140,24 +134,24 @@ var UpdateMe = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { u.Respond(w, http.StatusUnauthorized, "Failure", "Password incorrect. Please try again", nil) return } - + // validate input validate := validator.New() - if err := validate.Var(user.Email, "email"); user.Email != "" && err != nil { + if err := validate.Var(email, "email"); email != "" && err != nil { u.Respond(w, http.StatusBadRequest, "Failure", "Invalid input email: "+err.Error(), nil) return } - if err := validate.Var(user.Name, "min=5,max=20"); user.Name != "" && err != nil { + if err := validate.Var(name, "min=5,max=20"); name != "" && err != nil { u.Respond(w, http.StatusBadRequest, "Failure", "Invalid input name: "+err.Error(), nil) return } // update me _, err = db.Exec(`UPDATE users SET name = $1, email = $2 WHERE id = $3`, user.Name, user.Email, decoded.UserId) if err != nil { - u.Respond(w, http.StatusBadRequest, "Failure", "Something went wrong when update your account.. Please try again", nil) + u.Respond(w, http.StatusBadRequest, "Failure", "Something went wrong when update your account. Please try again", nil) return } - + // everything Ok u.Respond(w, http.StatusOK, "Success", "Success update your account!", map[string]interface{}{ "name": user.Name, "email": user.Email, @@ -169,6 +163,7 @@ var DeleteMe = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { // get decoded token from middleware decoded := r.Context().Value("user").(*models.Token) user := &models.User{ + ID: decoded.UserId, IsActive: false, } // convert json -> user object @@ -178,23 +173,22 @@ var DeleteMe = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { return } // get user info - var password string - err = db.QueryRow(`SELECT name, email, password FROM users WHERE id = $1`, decoded.UserId).Scan(&user.Name, &user.Email, &password) - if err != nil { + inputPassword := user.Password + if err := user.GetOneById(db); err != nil { u.Respond(w, http.StatusBadRequest, "Failure", "Something went wrong when collect your account. Please try again", nil) return } - // confirm password - if password != user.Password { + // if input password not equal to database password + if inputPassword != user.Password { u.Respond(w, http.StatusUnauthorized, "Failure", "Password incorrect. Please try again", nil) return } - // update me - _, err = db.Exec(`UPDATE users SET is_active = $1 WHERE id = $2`, user.IsActive, decoded.UserId) + // update field is_active to false + _, err = db.Exec(`UPDATE users SET is_active = $1 WHERE id = $2`, user.IsActive, user.ID) if err != nil { u.Respond(w, http.StatusBadRequest, "Failure", "Something went wrong when delete your account. Please try again", nil) return } - + // everything Ok u.Respond(w, http.StatusOK, "Success", "Success delete your account!", nil) } diff --git a/models/task.go b/models/task.go index f4543ce5e..bfa17f107 100644 --- a/models/task.go +++ b/models/task.go @@ -1,11 +1,30 @@ package models -import "time" +import ( + "database/sql" + "time" +) type Task struct { - ID int32 `json:"id" validate:"omitempty"` + ID uint32 `json:"id" validate:"omitempty"` Name string `json:"name" validate:"required"` Content string `json:"content"` CreatedAt time.Time `json:"createdAt"` UserId uint32 `json:"userId"` } + +func (t *Task) InsertOne(db *sql.DB) error { + err := db.QueryRow(`INSERT INTO tasks(name, content, user_id) VALUES($3, $2, $1) RETURNING name, content, created_at`, t.UserId, t.Content, t.Name).Scan(&t.Name, &t.Content, &t.CreatedAt) + if err != nil { + return err + } + return nil +} + +func (t *Task) GetOneByUserId(db *sql.DB) error { + err := db.QueryRow(`UPDATE tasks SET name = $3 WHERE id = $1 AND user_id = $2 RETURNING name, content, created_at`, t.ID, t.UserId, t.Name).Scan(&t.Name, &t.Content, &t.CreatedAt) + if err != nil { + return err + } + return nil +} diff --git a/models/token.go b/models/token.go new file mode 100644 index 000000000..9c86d0975 --- /dev/null +++ b/models/token.go @@ -0,0 +1,19 @@ +package models + +import ( + "os" + + "github.com/dgrijalva/jwt-go" +) + +type Token struct { + UserId uint32 + LimitDayTasks uint + jwt.StandardClaims +} + +func (t *Token) CreateToken() string { + token := jwt.NewWithClaims(jwt.GetSigningMethod("HS256"), t) + tokenString, _ := token.SignedString([]byte(os.Getenv("SECRET_TOKEN"))) + return tokenString +} diff --git a/models/user.go b/models/user.go index 916068df3..9e70fb62a 100644 --- a/models/user.go +++ b/models/user.go @@ -1,14 +1,9 @@ package models import ( - "github.com/dgrijalva/jwt-go" + "database/sql" ) -type Token struct { - UserId uint32 - LimitDayTasks uint - jwt.StandardClaims -} type User struct { ID uint32 `json:"id" validate:"omitempty"` Email string `json:"email" validate:"required,email"` @@ -18,3 +13,27 @@ type User struct { IsActive bool `json:"isActive"` LimitDayTasks uint `json:"limitDayTasks" validate:"omitempty"` } + +func (u *User) InsertOne(db *sql.DB) error { + err := db.QueryRow(`INSERT INTO users(name, email, password) VALUES($1, $2, $3) RETURNING id, name, email`, u.Name, u.Email, u.Password).Scan(&u.ID, &u.Name, &u.Email) + if err != nil { + return err + } + return nil +} + +func (u *User) GetOneById(db *sql.DB) error { + err := db.QueryRow(`SELECT id, name, email, password, is_payment, is_active, limit_day_tasks FROM users WHERE id = $1`, u.ID).Scan(&u.ID, &u.Name, &u.Email, &u.Password, &u.IsPayment, &u.IsActive, &u.LimitDayTasks) + if err != nil { + return err + } + return nil +} + +func (u *User) GetOneByEmail(db *sql.DB) error { + err := db.QueryRow(`SELECT id, name, email, password, is_payment, is_active, limit_day_tasks FROM users WHERE email = $1`, u.Email).Scan(&u.ID, &u.Name, &u.Email, &u.Password, &u.IsPayment, &u.IsActive, &u.LimitDayTasks) + if err != nil { + return err + } + return nil +} From 8f2e24fff8d7d8ea05c7b00b3810d1fe0bf2b3f5 Mon Sep 17 00:00:00 2001 From: Quang Ha Date: Sun, 26 Jun 2022 23:50:57 +0700 Subject: [PATCH 12/41] fix updateMe function --- controllers/task.go | 2 +- controllers/user.go | 30 +++++++++++++++++++----------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/controllers/task.go b/controllers/task.go index d71678fa7..c02678203 100644 --- a/controllers/task.go +++ b/controllers/task.go @@ -118,7 +118,7 @@ var Edit = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { return } - u.Respond(w, http.StatusCreated, "Success", "Success create task", map[string]interface{}{ + u.Respond(w, http.StatusCreated, "Success", "Success update task", map[string]interface{}{ "name": task.Name, "content": task.Content, "created_at": task.CreatedAt, diff --git a/controllers/user.go b/controllers/user.go index 5e725cc0e..8931ee198 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -3,6 +3,7 @@ package controllers import ( "database/sql" "encoding/json" + "fmt" "net/http" "github.com/go-playground/validator/v10" @@ -50,7 +51,7 @@ var Login = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { u.Respond(w, http.StatusBadRequest, "Failure", "Invalid input format: "+err.Error(), nil) return } - + fmt.Println(user) var ( email string = user.Email password string = user.Password @@ -124,6 +125,17 @@ var UpdateMe = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { email string = user.Email password string = user.Password ) + // validate input + validate := validator.New() + if err := validate.Var(email, "email"); email != "" && err != nil { + u.Respond(w, http.StatusBadRequest, "Failure", "Invalid input email: "+err.Error(), nil) + return + } + + if err := validate.Var(name, "min=5,max=20"); name != "" && err != nil { + u.Respond(w, http.StatusBadRequest, "Failure", "Invalid input name: "+err.Error(), nil) + return + } if err := user.GetOneById(db); err != nil { u.Respond(w, http.StatusBadRequest, "Failure", "Something went wrong when collect your account. Please try again", nil) @@ -134,19 +146,15 @@ var UpdateMe = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { u.Respond(w, http.StatusUnauthorized, "Failure", "Password incorrect. Please try again", nil) return } - // validate input - validate := validator.New() - if err := validate.Var(email, "email"); email != "" && err != nil { - u.Respond(w, http.StatusBadRequest, "Failure", "Invalid input email: "+err.Error(), nil) - return + // if valid value => overwrite new value + if name != "" { + user.Name = name } - - if err := validate.Var(name, "min=5,max=20"); name != "" && err != nil { - u.Respond(w, http.StatusBadRequest, "Failure", "Invalid input name: "+err.Error(), nil) - return + if email != "" { + user.Email = email } // update me - _, err = db.Exec(`UPDATE users SET name = $1, email = $2 WHERE id = $3`, user.Name, user.Email, decoded.UserId) + _, err = db.Exec(`UPDATE users SET name = $1, email = $2 WHERE id = $3`, user.Name, user.Email, user.ID) if err != nil { u.Respond(w, http.StatusBadRequest, "Failure", "Something went wrong when update your account. Please try again", nil) return From 1c17843f82754bff7a59bad91bc873e1e67b8122 Mon Sep 17 00:00:00 2001 From: Quang Ha Date: Mon, 27 Jun 2022 17:55:33 +0700 Subject: [PATCH 13/41] add user api testing with httptest --- controllers/controllers_main_test.go | 38 ++++++ controllers/task_test.go | 1 + controllers/user.go | 9 +- controllers/user_test.go | 168 +++++++++++++++++++++++++++ 4 files changed, 211 insertions(+), 5 deletions(-) create mode 100644 controllers/controllers_main_test.go create mode 100644 controllers/task_test.go create mode 100644 controllers/user_test.go diff --git a/controllers/controllers_main_test.go b/controllers/controllers_main_test.go new file mode 100644 index 000000000..875e94219 --- /dev/null +++ b/controllers/controllers_main_test.go @@ -0,0 +1,38 @@ +package controllers_test + +import ( + "os" + "testing" + + "net/http" + "net/http/httptest" + + "github.com/manabie-com/togo/app" +) + +var a app.App + +type response struct { + Status string + Message string + Data map[string]interface{} +} + +func TestMain(m *testing.M) { + a = app.App{} + a.Init() + code := m.Run() + os.Exit(code) +} + +func executeRequest(r *http.Request) *httptest.ResponseRecorder { + rr := httptest.NewRecorder() + a.Router.ServeHTTP(rr, r) + return rr +} + +func checkResponseCode(t *testing.T, expected, actual int) { + if expected != actual { + t.Errorf("Expected response code %d. Got %d\n", expected, actual) + } +} diff --git a/controllers/task_test.go b/controllers/task_test.go new file mode 100644 index 000000000..3e934e6fd --- /dev/null +++ b/controllers/task_test.go @@ -0,0 +1 @@ +package controllers_test diff --git a/controllers/user.go b/controllers/user.go index 8931ee198..3e460fd1b 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -138,7 +138,7 @@ var UpdateMe = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { } if err := user.GetOneById(db); err != nil { - u.Respond(w, http.StatusBadRequest, "Failure", "Something went wrong when collect your account. Please try again", nil) + u.Respond(w, http.StatusBadRequest, "Failure", "Your email invalid. Please try again", nil) return } // confirm password @@ -161,9 +161,8 @@ var UpdateMe = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { } // everything Ok u.Respond(w, http.StatusOK, "Success", "Success update your account!", map[string]interface{}{ - "name": user.Name, - "email": user.Email, - "is_payment": user.IsPayment, + "name": user.Name, + "email": user.Email, }) } @@ -198,5 +197,5 @@ var DeleteMe = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { return } // everything Ok - u.Respond(w, http.StatusOK, "Success", "Success delete your account!", nil) + u.Respond(w, http.StatusNoContent, "Success", "Success delete your account!", nil) } diff --git a/controllers/user_test.go b/controllers/user_test.go new file mode 100644 index 000000000..2611bd07a --- /dev/null +++ b/controllers/user_test.go @@ -0,0 +1,168 @@ +package controllers_test + +import ( + "bytes" + "encoding/json" + "net/http" + "testing" +) + +func TestSignUp(t *testing.T) { + payload := []byte(`{ + "name": "test user", + "email": "test21@example.com", + "password": "123456" + }`) + req, _ := http.NewRequest("POST", "/api/users/signup", bytes.NewBuffer(payload)) + req.Header.Set("Content-Type", "application/json") + res := executeRequest(req) + checkResponseCode(t, http.StatusCreated, res.Code) + + var r response + json.Unmarshal(res.Body.Bytes(), &r) + + // json.NewDecoder(res.Body).Decode(&m) + if r.Status != "Success" { + t.Errorf("Expected Status field to be 'Success' but. Got '%v'", r.Status) + } + + if r.Message != "Created Account" { + t.Errorf("Expected Message field to be 'Created Account'. Got '%v'", r.Message) + } + + if r.Data["email"] != "test21@example.com" { + t.Errorf("Expected type of Data to be 'test21@example.com'. Got '%v'", r.Data["email"]) + } + + if r.Data["name"] != "test user" { + t.Errorf("Expected type of Data to be 'test user'. Got '%v'", r.Data["name"]) + } + // the id is compared to 1.0 because JSON unmarshaling converts numbers to + // floats, when the target is a map[string]interface{} +} + +func TestLogin(t *testing.T) { + payload := []byte(`{ + "email": "test2@example.com", + "password": "123456" + }`) + req, _ := http.NewRequest("POST", "/api/users/login", bytes.NewBuffer(payload)) + req.Header.Set("Content-Type", "application/json") + res := executeRequest(req) + checkResponseCode(t, http.StatusOK, res.Code) + + var r response + json.Unmarshal(res.Body.Bytes(), &r) + + // json.NewDecoder(res.Body).Decode(&m) + if r.Status != "Success" { + t.Errorf("Expected Status field to be 'Success' but. Got '%v'", r.Status) + } + + if r.Message != "Login Success" { + t.Errorf("Expected Message field to be 'Created Account'. Got '%v'", r.Message) + } + + if r.Data["email"] != "test2@example.com" { + t.Errorf("Expected type of Data to be 'test2@example.com'. Got '%v'", r.Data["email"]) + } + + if r.Data["name"] != "test user1" { + t.Errorf("Expected type of Data to be 'test user1'. Got '%v'", r.Data["name"]) + } + // the id is compared to 1.0 because JSON unmarshaling converts numbers to + // floats, when the target is a map[string]interface{} +} + +func TestGetMe(t *testing.T) { + req, _ := http.NewRequest("GET", "/api/users/me", nil) + token := "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjIzLCJMaW1pdERheVRhc2tzIjoxMH0.L0uyMcBzvzzK6c1hisfXuL0Cp-Gbwu6qiSMVq0Ojwaw" + req.Header.Set("Authorization", token) + res := executeRequest(req) + checkResponseCode(t, http.StatusOK, res.Code) + + var r response + json.Unmarshal(res.Body.Bytes(), &r) + + // json.NewDecoder(res.Body).Decode(&m) + if r.Status != "Success" { + t.Errorf("Expected Status field to be 'Success' but. Got '%v'", r.Status) + } + + if r.Message != "Success" { + t.Errorf("Expected Message field to be 'Success'. Got '%v'", r.Message) + } + + if r.Data["email"] != "test2@example.com" { + t.Errorf("Expected field of Data email to be 'test2@example.com'. Got '%v'", r.Data["email"]) + } + + if r.Data["name"] != "test user1" { + t.Errorf("Expected field of Data name to be 'test user'. Got '%v'", r.Data["name"]) + } + if r.Data["is_payment"] != false { + t.Errorf("Expected field of Data is_payment to be 'true'. Got '%v'", r.Data["is_payment"]) + } + if r.Data["limit_day_tasks"] != 10.0 { + t.Errorf("Expected field of Data limit_day_tasks to be '10'. Got '%v'", r.Data["limit_day_tasks"]) + } +} +func TestUpdateMe(t *testing.T) { + payload := []byte(`{ + "name": "updated test user", + "email": "updatedtest@example.com", + "password": "123456" + }`) + req, _ := http.NewRequest("PATCH", "/api/users/edit", bytes.NewBuffer(payload)) + token := "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjIwLCJMaW1pdERheVRhc2tzIjoxMH0.8UHK64DHDcly-fSB22HdsVAoh5y_P_YhN-eX_SZoO_w" + req.Header.Set("Authorization", token) + res := executeRequest(req) + checkResponseCode(t, http.StatusOK, res.Code) + + var r response + json.Unmarshal(res.Body.Bytes(), &r) + + // json.NewDecoder(res.Body).Decode(&m) + if r.Status != "Success" { + t.Errorf("Expected Status field to be 'Success' but. Got '%v'", r.Status) + } + + if r.Message != "Success update your account!" { + t.Errorf("Expected Message field to be 'Success'. Got '%v'", r.Message) + } + + if r.Data["email"] != "updatedtest@example.com" { + t.Errorf("Expected field of Data email to be 'updatedtest@example.com'. Got '%v'", r.Data["email"]) + } + + if r.Data["name"] != "updated test user" { + t.Errorf("Expected field of Data name to be 'updated test user'. Got '%v'", r.Data["name"]) + } +} + +func TestDeleteMe(t *testing.T) { + payload := []byte(`{ + "password": "123456" + }`) + req, _ := http.NewRequest("DELETE", "/api/users/delete", bytes.NewBuffer(payload)) + token := "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjIzLCJMaW1pdERheVRhc2tzIjoxMH0.L0uyMcBzvzzK6c1hisfXuL0Cp-Gbwu6qiSMVq0Ojwaw" + req.Header.Set("Authorization", token) + res := executeRequest(req) + checkResponseCode(t, http.StatusNoContent, res.Code) + + var r response + json.Unmarshal(res.Body.Bytes(), &r) + + // json.NewDecoder(res.Body).Decode(&m) + if r.Status != "Success" { + t.Errorf("Expected Status field to be 'Success' but. Got '%v'", r.Status) + } + + if r.Message != "Success delete your account!" { + t.Errorf("Expected Message field to be 'Success'. Got '%v'", r.Message) + } + + if r.Data != nil { + t.Errorf("Expected field of Data to be 'nil'. Got '%v'", r.Data) + } +} From e8d6428050e542c595fc905fb58651bad57e9c86 Mon Sep 17 00:00:00 2001 From: Quang Ha Date: Tue, 28 Jun 2022 17:02:02 +0700 Subject: [PATCH 14/41] implement task, payment integration test and restructure source code from controllers to models folder --- app/app.go | 3 +- controllers/auth.go | 8 +- controllers/controllers_main_test.go | 5 +- controllers/payment.go | 7 +- controllers/payment_test.go | 30 +++++++ controllers/task.go | 85 ++++++++++--------- controllers/task_test.go | 120 +++++++++++++++++++++++++++ controllers/user.go | 81 ++++++++++-------- controllers/user_test.go | 91 +++++++------------- main.go | 1 - models/task.go | 46 +++++++++- models/user.go | 41 ++++++++- utils/utils.go | 13 ++- 13 files changed, 376 insertions(+), 155 deletions(-) create mode 100644 controllers/payment_test.go diff --git a/app/app.go b/app/app.go index a3c8f6f33..f9f02275f 100644 --- a/app/app.go +++ b/app/app.go @@ -9,6 +9,7 @@ import ( "github.com/gorilla/mux" "github.com/joho/godotenv" + _ "github.com/lib/pq" "github.com/manabie-com/togo/controllers" c "github.com/manabie-com/togo/controllers" ) @@ -19,7 +20,7 @@ type App struct { } func (a *App) Init() { - if err := godotenv.Load(); err != nil { + if err := godotenv.Load("C:\\Users\\quang\\Desktop\\Project\\Go\\internship\\togo\\.env"); err != nil { log.Fatal("Error loading .env file") } connectURL := os.Getenv("CONNECT_STR") diff --git a/controllers/auth.go b/controllers/auth.go index 71e39c682..00706fe63 100644 --- a/controllers/auth.go +++ b/controllers/auth.go @@ -28,13 +28,13 @@ var JwtAuthentication = func(next http.Handler) http.Handler { tokenHeader := r.Header.Get("Authorization") //Grab the token from the header if tokenHeader == "" { //Token is missing, returns with error code 403 Unauthorized - u.Respond(w, http.StatusForbidden, "Failure", "Missing auth token", nil) + u.FailureRespond(w, http.StatusForbidden, "Missing auth token") return } splitted := strings.Split(tokenHeader, " ") //The token normally comes in format `Bearer {token-body}`, we check if the retrieved token matched this requirement if len(splitted) != 2 { - u.Respond(w, http.StatusForbidden, "Failure", "Invalid/Malformed auth token", nil) + u.FailureRespond(w, http.StatusForbidden, "Invalid/Malformed auth token") return } @@ -44,12 +44,12 @@ var JwtAuthentication = func(next http.Handler) http.Handler { return []byte(os.Getenv("SECRET_TOKEN")), nil }) if err != nil { //Malformed token, returns with http code 403 as usual - u.Respond(w, http.StatusForbidden, "Failure", "Malformed authentication token", err.Error()) + u.FailureRespond(w, http.StatusForbidden, "Malformed authentication token") return } if !token.Valid { //Token is invalid, maybe not signed on this server - u.Respond(w, http.StatusForbidden, "Failure", "Token is not valid.", nil) + u.FailureRespond(w, http.StatusForbidden, "Token is not valid.") return } //Everything went well, proceed with the request and set the caller to the user retrieved from the parsed token diff --git a/controllers/controllers_main_test.go b/controllers/controllers_main_test.go index 875e94219..dc17df23c 100644 --- a/controllers/controllers_main_test.go +++ b/controllers/controllers_main_test.go @@ -10,7 +10,10 @@ import ( "github.com/manabie-com/togo/app" ) -var a app.App +var ( + a app.App + r response +) type response struct { Status string diff --git a/controllers/payment.go b/controllers/payment.go index 2798cc191..fc47270f4 100644 --- a/controllers/payment.go +++ b/controllers/payment.go @@ -11,17 +11,18 @@ import ( func Payment(db *sql.DB, w http.ResponseWriter, r *http.Request) { decoded := r.Context().Value("user").(*models.Token) user := &models.User{ + ID: decoded.UserId, LimitDayTasks: 20, IsPayment: true, } - err := db.QueryRow(`UPDATE users SET is_payment = $1, limit_day_tasks = $2 WHERE id = $3 RETURNING name, email, limit_day_tasks`, user.IsPayment, user.LimitDayTasks, decoded.UserId).Scan(&user.Name, &user.Email, &user.LimitDayTasks) + err := user.UpgradePremium(db) if err != nil { - u.Respond(w, http.StatusBadRequest, "Failure", "Something went wrong. Please try again", nil) + u.FailureRespond(w, http.StatusBadRequest, "Something went wrong. Please try again") return } - u.Respond(w, http.StatusOK, "Success", "Success upgrade Premium account. Please login again to try new upgrade", map[string]interface{}{ + u.SuccessRespond(w, http.StatusOK, "Success upgrade Premium account. Please login again to try new upgrade", map[string]interface{}{ "name": user.Name, "email": user.Email, "is_payment": user.IsPayment, diff --git a/controllers/payment_test.go b/controllers/payment_test.go new file mode 100644 index 000000000..970dec6d0 --- /dev/null +++ b/controllers/payment_test.go @@ -0,0 +1,30 @@ +package controllers_test + +import ( + "encoding/json" + "net/http" + "testing" +) + +// Pass ✅ +func TestPayment(t *testing.T) { + req, _ := http.NewRequest("POST", "/api/payments", nil) + token := "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjYsIkxpbWl0RGF5VGFza3MiOjEwfQ.p71B7Pd2SydLxeVm5LqZIQXgljHAOJN9z5we4wlW2lA" + req.Header.Set("Authorization", token) + res := executeRequest(req) + + json.Unmarshal(res.Body.Bytes(), &r) + checkResponseCode(t, http.StatusOK, res.Code) + if r.Data["name"] != "user1" { + t.Errorf("Expected user name is 'user1'. Got '%v'", r.Data["name"]) + } + if r.Data["email"] != "user1@gmail.com" { + t.Errorf("Expected user email is 'user1@gmail.com'. Got '%v'", r.Data["email"]) + } + if r.Data["is_payment"] != true { + t.Errorf("Expected user is_payment field is 'true'. Got '%v'", r.Data["is_payment"]) + } + if r.Data["limit_day_tasks"] != 20.0 { + t.Errorf("Expected user limit task field is '20'. Got '%v'", r.Data["limit_day_tasks"]) + } +} diff --git a/controllers/task.go b/controllers/task.go index c02678203..67c8b36cd 100644 --- a/controllers/task.go +++ b/controllers/task.go @@ -3,6 +3,7 @@ package controllers import ( "database/sql" "encoding/json" + "fmt" "net/http" "strconv" @@ -15,21 +16,16 @@ import ( var GetTasks = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { // get user id here decoded := r.Context().Value("user").(*models.Token) - rows, err := db.Query(`SELECT * FROM tasks WHERE user_id = $1`, decoded.UserId) + task := &models.Task{ + UserId: decoded.UserId, + } + tasks, err := task.GetTasksByUserId(db) if err != nil { - u.Respond(w, http.StatusNotFound, "Failure", err.Error(), nil) + u.FailureRespond(w, http.StatusNotFound, err.Error()) return } - - var tasks []*models.Task - - for rows.Next() { - var task = &models.Task{} - rows.Scan(&task.ID, &task.Name, &task.Content, &task.CreatedAt, &task.UserId) - tasks = append(tasks, task) - } //Everything OK - u.Respond(w, http.StatusOK, "Success", "Success", tasks) + u.SuccessRespond(w, http.StatusOK, "Success", tasks) } var GetTask = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { @@ -39,7 +35,7 @@ var GetTask = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { id := func(id string) uint32 { u64, err := strconv.ParseUint(id, 10, 32) if err != nil { - u.Respond(w, http.StatusBadRequest, "Failure", "ID must be a number type.", nil) + u.FailureRespond(w, http.StatusBadRequest, "ID must be a number type.") } return uint32(u64) }(mux.Vars(r)["id"]) @@ -48,13 +44,13 @@ var GetTask = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { ID: id, UserId: decoded.UserId, } - - if err := task.GetOneByUserId(db); err != nil { - u.Respond(w, http.StatusNotFound, "Failure", "Not found task", nil) + err := task.GetTaskByUserId(db) + if err != nil { + u.FailureRespond(w, http.StatusNotFound, "Not found task") return } - u.Respond(w, http.StatusOK, "Success", "Success", map[string]interface{}{ + u.SuccessRespond(w, http.StatusOK, "Success", map[string]interface{}{ "name": task.Name, "content": task.Content, "created_at": task.CreatedAt, @@ -70,31 +66,29 @@ var Add = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { // json body -> task object err := json.NewDecoder(r.Body).Decode(task) if err != nil { - u.Respond(w, http.StatusBadRequest, "Failure", "invalid request", nil) + u.FailureRespond(w, http.StatusBadRequest, "invalid request") return } // validate task object validate := validator.New() - if err = validate.Struct(task); err != nil { - u.Respond(w, http.StatusBadRequest, "Failure", err.Error(), nil) + err = validate.Struct(task) + if err != nil { + u.FailureRespond(w, http.StatusBadRequest, err.Error()) return } - // check limit day tasks - var recordsLength uint - _ = db.QueryRow(`SELECT COUNT(id) - FROM tasks - WHERE created_at >= NOW() - INTERVAL '24 HOURS' AND user_id = $1`, decoded.UserId).Scan(&recordsLength) - if recordsLength == decoded.LimitDayTasks { - u.Respond(w, http.StatusBadRequest, "Failure", "Today tasks had limited, Please Comeback tomorrow.", nil) + // check today tasks limit + if task.IsLimit(db, decoded.LimitDayTasks) { + u.FailureRespond(w, http.StatusBadRequest, "Today tasks had limited, Please Comeback tomorrow.") return } // insert database - if task.InsertOne(db); err != nil { - u.Respond(w, http.StatusBadRequest, "Failure", err.Error(), nil) + err = task.InsertTask(db) + if err != nil { + u.FailureRespond(w, http.StatusBadRequest, err.Error()) return } //everything OK - u.Respond(w, http.StatusCreated, "Success", "Success create task", map[string]interface{}{ + u.SuccessRespond(w, http.StatusCreated, "Success create task", map[string]interface{}{ "name": task.Name, "content": task.Content, "created_at": task.CreatedAt, @@ -109,16 +103,27 @@ var Edit = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { } err := json.NewDecoder(r.Body).Decode(task) if err != nil { - u.Respond(w, http.StatusBadRequest, "Failure", err.Error(), nil) + u.FailureRespond(w, http.StatusBadRequest, err.Error()) return } - err = db.QueryRow(`UPDATE tasks SET name = $3 WHERE id = $1 AND user_id = $2 RETURNING name, content, created_at`, id, decoded.UserId, task.Name).Scan(&task.Name, &task.Content, &task.CreatedAt) + var ( + name string = task.Name + content string = task.Content + ) + fmt.Println(task) + if name != "" { + task.Name = name + } + if content != "" { + task.Content = content + } + err = task.UpdateTaskById(db, id) if err != nil { - u.Respond(w, http.StatusBadRequest, "Failure", err.Error(), nil) + u.FailureRespond(w, http.StatusBadRequest, err.Error()) return } - - u.Respond(w, http.StatusCreated, "Success", "Success update task", map[string]interface{}{ + fmt.Println(task) + u.SuccessRespond(w, http.StatusOK, "Success update task", map[string]interface{}{ "name": task.Name, "content": task.Content, "created_at": task.CreatedAt, @@ -128,10 +133,16 @@ var Edit = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { var Delete = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { decoded := r.Context().Value("user").(*models.Token) id := mux.Vars(r)["id"] - _, err := db.Exec(`DELETE FROM tasks WHERE id = $1 AND user_id = $2`, id, decoded.UserId) + + task := &models.Task{ + UserId: decoded.UserId, + } + + err := task.DeleteTaskById(db, id) if err != nil { - u.Respond(w, http.StatusNotFound, "Failure", err.Error(), nil) + u.FailureRespond(w, http.StatusBadRequest, err.Error()) return } - u.Respond(w, http.StatusCreated, "Success", "Success delete task", nil) + + u.SuccessRespond(w, http.StatusNoContent, "Success delete task", nil) } diff --git a/controllers/task_test.go b/controllers/task_test.go index 3e934e6fd..aa354fb9d 100644 --- a/controllers/task_test.go +++ b/controllers/task_test.go @@ -1 +1,121 @@ package controllers_test + +import ( + "bytes" + "encoding/json" + "net/http" + "testing" +) + +// Pass ✅ +func TestGetTasks(t *testing.T) { + type tasksRes struct { + Status string + Message string + Data []map[string]interface{} + } + // get user id here + req, _ := http.NewRequest("GET", "/api/tasks", nil) + token := "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjYsIkxpbWl0RGF5VGFza3MiOjEwfQ.p71B7Pd2SydLxeVm5LqZIQXgljHAOJN9z5we4wlW2lA" + req.Header.Set("Authorization", token) + res := executeRequest(req) + checkResponseCode(t, http.StatusOK, res.Code) + var r tasksRes + json.Unmarshal(res.Body.Bytes(), &r) + + // json.NewDecoder(res.Body).Decode(&m) + if r.Status != "Success" { + t.Errorf("Expected Status field to be 'Success' but. Got '%v'", r.Status) + } + + if len(r.Data) != 8 { + t.Errorf("Expected tasks length to be '7'. Got '%v'", len(r.Data)) + } + + for _, val := range r.Data { + if val["id"] == 4 { + if val["name"] != "task name" { + t.Errorf("Expected type of Data to be 'task name' value. Got '%v'", val["name"]) + } + if val["content"] != "subtask1 \n subtask2" { + t.Errorf("Expected type of Data to be 'subtask1 \n subtask2' value. Got '%v'", val["content"]) + } + } + } +} + +// Pass ✅ +func TestGetTask(t *testing.T) { + req, _ := http.NewRequest("GET", "/api/tasks/4", nil) + // auth token + token := "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjYsIkxpbWl0RGF5VGFza3MiOjEwfQ.p71B7Pd2SydLxeVm5LqZIQXgljHAOJN9z5we4wlW2lA" + req.Header.Set("Authorization", token) + + res := executeRequest(req) + json.Unmarshal(res.Body.Bytes(), &r) + checkResponseCode(t, http.StatusOK, res.Code) + if r.Data["name"] != "task name" { + t.Errorf("Expected type of Data to be 'task name' value. Got '%v'", r.Data["name"]) + } + if r.Data["content"] != "subtask1 \n subtask2" { + t.Errorf("Expected type of Data to be 'subtask1 \n subtask2' value. Got '%v'", r.Data["content"]) + } +} + +// Pass ✅ +func TestAdd(t *testing.T) { + payload := []byte(`{ + "name" : "task name", + "content" : "task content" + }`) + + req, _ := http.NewRequest("POST", "/api/tasks/add", bytes.NewBuffer(payload)) + token := "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjYsIkxpbWl0RGF5VGFza3MiOjEwfQ.p71B7Pd2SydLxeVm5LqZIQXgljHAOJN9z5we4wlW2lA" + req.Header.Set("Authorization", token) + res := executeRequest(req) + json.Unmarshal(res.Body.Bytes(), &r) + checkResponseCode(t, http.StatusCreated, res.Code) + + if r.Data["name"] != "task name" { + t.Errorf("Expected task name is 'task name'. Got '%v'", r.Data["name"]) + } + if r.Data["content"] != "task content" { + t.Errorf("Expected task content is 'task content'. Got '%v'", r.Data["content"]) + } +} + +// Pass ✅ +func TestEdit(t *testing.T) { + payload := []byte(`{ + "name" : "task name", + "content" : "task content" + }`) + req, _ := http.NewRequest("PATCH", "/api/tasks/4", bytes.NewBuffer(payload)) + token := "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjYsIkxpbWl0RGF5VGFza3MiOjEwfQ.p71B7Pd2SydLxeVm5LqZIQXgljHAOJN9z5we4wlW2lA" + req.Header.Set("Authorization", token) + res := executeRequest(req) + + json.Unmarshal(res.Body.Bytes(), &r) + checkResponseCode(t, http.StatusOK, res.Code) + if r.Data["name"] != "task name" { + t.Errorf("Expected task name is 'task name'. Got '%v'", r.Data["name"]) + } + if r.Data["content"] != "task content" { + t.Errorf("Expected task content is 'task content'. Got '%v'", r.Data["content"]) + } +} + +// Pass ✅ +func TestDelete(t *testing.T) { + req, _ := http.NewRequest("DELETE", "/api/tasks/3", nil) + token := "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjYsIkxpbWl0RGF5VGFza3MiOjEwfQ.p71B7Pd2SydLxeVm5LqZIQXgljHAOJN9z5we4wlW2lA" + req.Header.Set("Authorization", token) + res := executeRequest(req) + + json.Unmarshal(res.Body.Bytes(), &r) + checkResponseCode(t, http.StatusNoContent, res.Code) + if r.Data != nil { + t.Errorf("Expected type of Data to be 'nil' value. Got '%v'", r.Data) + } + t.Logf("Response %v", r) +} diff --git a/controllers/user.go b/controllers/user.go index 3e460fd1b..4df89a0bc 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -3,7 +3,6 @@ package controllers import ( "database/sql" "encoding/json" - "fmt" "net/http" "github.com/go-playground/validator/v10" @@ -17,26 +16,29 @@ var SignUp = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { LimitDayTasks: 10, } // decode json body to user - if err := json.NewDecoder(r.Body).Decode(user); err != nil { - u.Respond(w, http.StatusBadRequest, "Failure", "Invalid input format: "+err.Error(), nil) + err := json.NewDecoder(r.Body).Decode(user) + if err != nil { + u.FailureRespond(w, http.StatusBadRequest, "Invalid input format: "+err.Error()) return } // validate user object validate := validator.New() - if err := validate.Struct(user); err != nil { - u.Respond(w, http.StatusBadRequest, "Failure", "Invalid input field: "+err.Error(), nil) + err = validate.Struct(user) + if err != nil { + u.FailureRespond(w, http.StatusBadRequest, "Invalid input field: "+err.Error()) return } // insert database - if err := user.InsertOne(db); err != nil { - u.Respond(w, http.StatusBadRequest, "Failure", "Your email is duplicated, Please try again", nil) + err = user.InsertUser(db) + if err != nil { + u.FailureRespond(w, http.StatusBadRequest, "Your email is duplicated, Please try again") return } // send token jwt here tk := &models.Token{UserId: user.ID, LimitDayTasks: user.LimitDayTasks} tokenString := tk.CreateToken() // everything Ok - u.Respond(w, http.StatusCreated, "Success", "Created Account", map[string]interface{}{ + u.SuccessRespond(w, http.StatusCreated, "Created Account", map[string]interface{}{ "name": user.Name, "email": user.Email, "token": tokenString, @@ -48,29 +50,34 @@ var Login = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { // validate err := json.NewDecoder(r.Body).Decode(user) if err != nil { - u.Respond(w, http.StatusBadRequest, "Failure", "Invalid input format: "+err.Error(), nil) + u.FailureRespond(w, http.StatusBadRequest, "Invalid input format: "+err.Error()) return } - fmt.Println(user) + var ( email string = user.Email password string = user.Password ) // get user by email - if err := user.GetOneByEmail(db); err != nil { - u.Respond(w, http.StatusBadRequest, "Failure", "Not found account with your email, Please provide a valid email address!", nil) + err = user.GetUserByEmail(db) + if err != nil { + u.FailureRespond(w, http.StatusBadRequest, "Not found account with your email, Please provide a valid email address!") return } // if email exist and password incorrect if email != user.Email || password != user.Password { - u.Respond(w, http.StatusUnauthorized, "Failure", "Password incorrect", nil) + u.FailureRespond(w, http.StatusUnauthorized, "Password incorrect") return } // create message case 1: Active user, case 2: unActive user var message = "Login Success" if !user.IsActive { message = "Welcome back" - _, _ = db.Exec(`UPDATE users SET is_active = $1 WHERE id = $2`, true, user.ID) + err = user.ActiveUser(db) + if err != nil { + u.FailureRespond(w, http.StatusInternalServerError, "Something went wrong"+err.Error()) + return + } } // if email and password OK //Create JWT token @@ -80,7 +87,7 @@ var Login = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { } tokenString := tk.CreateToken() // everything Ok - u.Respond(w, http.StatusOK, "Success", message, map[string]interface{}{ + u.SuccessRespond(w, http.StatusOK, message, map[string]interface{}{ "name": user.Name, "email": user.Email, "token": tokenString, @@ -94,12 +101,13 @@ var GetMe = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { ID: decoded.UserId, } // query - if err := user.GetOneById(db); err != nil { - u.Respond(w, http.StatusBadRequest, "Failure", "Something went wrong when collect your account. Please try again", nil) + err := user.GetUserById(db) + if err != nil { + u.FailureRespond(w, http.StatusInternalServerError, "Something went wrong when collect your account. Please try again"+err.Error()) return } // everything Ok - u.Respond(w, http.StatusOK, "Success", "Success", map[string]interface{}{ + u.SuccessRespond(w, http.StatusOK, "Success", map[string]interface{}{ "name": user.Name, "email": user.Email, "is_payment": user.IsPayment, @@ -116,7 +124,7 @@ var UpdateMe = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { // convert json -> user object err := json.NewDecoder(r.Body).Decode(user) if err != nil { - u.Respond(w, http.StatusBadRequest, "Failure", "Invalid input format: "+err.Error(), nil) + u.FailureRespond(w, http.StatusBadRequest, "Invalid input format: "+err.Error()) return } // get user info @@ -127,23 +135,23 @@ var UpdateMe = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { ) // validate input validate := validator.New() - if err := validate.Var(email, "email"); email != "" && err != nil { - u.Respond(w, http.StatusBadRequest, "Failure", "Invalid input email: "+err.Error(), nil) + if err := validate.Var(email, "email,min=10,max=30"); email != "" && err != nil { + u.FailureRespond(w, http.StatusBadRequest, "Invalid input email: "+err.Error()) return } if err := validate.Var(name, "min=5,max=20"); name != "" && err != nil { - u.Respond(w, http.StatusBadRequest, "Failure", "Invalid input name: "+err.Error(), nil) + u.FailureRespond(w, http.StatusBadRequest, "Invalid input name: "+err.Error()) return } - - if err := user.GetOneById(db); err != nil { - u.Respond(w, http.StatusBadRequest, "Failure", "Your email invalid. Please try again", nil) + err = user.GetUserById(db) + if err != nil { + u.FailureRespond(w, http.StatusInternalServerError, "Somethings went wrong. Please try again"+err.Error()) return } // confirm password if password != user.Password { - u.Respond(w, http.StatusUnauthorized, "Failure", "Password incorrect. Please try again", nil) + u.FailureRespond(w, http.StatusUnauthorized, "Password incorrect. Please try again") return } // if valid value => overwrite new value @@ -154,13 +162,13 @@ var UpdateMe = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { user.Email = email } // update me - _, err = db.Exec(`UPDATE users SET name = $1, email = $2 WHERE id = $3`, user.Name, user.Email, user.ID) + err = user.UpdateUser(db) if err != nil { - u.Respond(w, http.StatusBadRequest, "Failure", "Something went wrong when update your account. Please try again", nil) + u.FailureRespond(w, http.StatusInternalServerError, "Something went wrong when update your account. Please try again"+err.Error()) return } // everything Ok - u.Respond(w, http.StatusOK, "Success", "Success update your account!", map[string]interface{}{ + u.SuccessRespond(w, http.StatusOK, "Success update your account!", map[string]interface{}{ "name": user.Name, "email": user.Email, }) @@ -176,26 +184,27 @@ var DeleteMe = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { // convert json -> user object err := json.NewDecoder(r.Body).Decode(user) if err != nil { - u.Respond(w, http.StatusBadRequest, "Failure", "Invalid input format: "+err.Error(), nil) + u.FailureRespond(w, http.StatusBadRequest, "Invalid input format: "+err.Error()) return } // get user info inputPassword := user.Password - if err := user.GetOneById(db); err != nil { - u.Respond(w, http.StatusBadRequest, "Failure", "Something went wrong when collect your account. Please try again", nil) + err = user.GetUserById(db) + if err != nil { + u.FailureRespond(w, http.StatusInternalServerError, "Something went wrong when collect your account. Please try again"+err.Error()) return } // if input password not equal to database password if inputPassword != user.Password { - u.Respond(w, http.StatusUnauthorized, "Failure", "Password incorrect. Please try again", nil) + u.FailureRespond(w, http.StatusUnauthorized, "Password incorrect. Please try again") return } // update field is_active to false - _, err = db.Exec(`UPDATE users SET is_active = $1 WHERE id = $2`, user.IsActive, user.ID) + err = user.DeleteUser(db) if err != nil { - u.Respond(w, http.StatusBadRequest, "Failure", "Something went wrong when delete your account. Please try again", nil) + u.FailureRespond(w, http.StatusInternalServerError, "Something went wrong when delete your account. Please try again"+err.Error()) return } // everything Ok - u.Respond(w, http.StatusNoContent, "Success", "Success delete your account!", nil) + u.SuccessRespond(w, http.StatusNoContent, "Success delete your account!", nil) } diff --git a/controllers/user_test.go b/controllers/user_test.go index 2611bd07a..b701c2e51 100644 --- a/controllers/user_test.go +++ b/controllers/user_test.go @@ -7,10 +7,11 @@ import ( "testing" ) +// Pass ✅ func TestSignUp(t *testing.T) { payload := []byte(`{ - "name": "test user", - "email": "test21@example.com", + "name": "user1", + "email": "user10@gmail.com", "password": "123456" }`) req, _ := http.NewRequest("POST", "/api/users/signup", bytes.NewBuffer(payload)) @@ -21,29 +22,20 @@ func TestSignUp(t *testing.T) { var r response json.Unmarshal(res.Body.Bytes(), &r) - // json.NewDecoder(res.Body).Decode(&m) - if r.Status != "Success" { - t.Errorf("Expected Status field to be 'Success' but. Got '%v'", r.Status) - } - - if r.Message != "Created Account" { - t.Errorf("Expected Message field to be 'Created Account'. Got '%v'", r.Message) + if r.Data["email"] != "user10@gmail.com" { + t.Errorf("Expected type of Data to be 'user10@gmail.com'. Got '%v'", r.Data["email"]) } - if r.Data["email"] != "test21@example.com" { - t.Errorf("Expected type of Data to be 'test21@example.com'. Got '%v'", r.Data["email"]) + if r.Data["name"] != "user1" { + t.Errorf("Expected type of Data to be 'user1'. Got '%v'", r.Data["name"]) } - - if r.Data["name"] != "test user" { - t.Errorf("Expected type of Data to be 'test user'. Got '%v'", r.Data["name"]) - } - // the id is compared to 1.0 because JSON unmarshaling converts numbers to - // floats, when the target is a map[string]interface{} + t.Logf("Response Message: %v", r.Message) } +// Pass ✅ func TestLogin(t *testing.T) { payload := []byte(`{ - "email": "test2@example.com", + "email": "user1@gmail.com", "password": "123456" }`) req, _ := http.NewRequest("POST", "/api/users/login", bytes.NewBuffer(payload)) @@ -54,51 +46,36 @@ func TestLogin(t *testing.T) { var r response json.Unmarshal(res.Body.Bytes(), &r) - // json.NewDecoder(res.Body).Decode(&m) - if r.Status != "Success" { - t.Errorf("Expected Status field to be 'Success' but. Got '%v'", r.Status) - } - - if r.Message != "Login Success" { - t.Errorf("Expected Message field to be 'Created Account'. Got '%v'", r.Message) + if r.Data["email"] != "user1@gmail.com" { + t.Errorf("Expected type of Data to be 'user1@gmail.com'. Got '%v'", r.Data["email"]) } - if r.Data["email"] != "test2@example.com" { - t.Errorf("Expected type of Data to be 'test2@example.com'. Got '%v'", r.Data["email"]) - } - - if r.Data["name"] != "test user1" { - t.Errorf("Expected type of Data to be 'test user1'. Got '%v'", r.Data["name"]) + if r.Data["name"] != "user1" { + t.Errorf("Expected type of Data to be 'user1'. Got '%v'", r.Data["name"]) } + t.Logf("Response Message: %v", r.Message) // the id is compared to 1.0 because JSON unmarshaling converts numbers to // floats, when the target is a map[string]interface{} } +// Pass ✅ func TestGetMe(t *testing.T) { req, _ := http.NewRequest("GET", "/api/users/me", nil) - token := "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjIzLCJMaW1pdERheVRhc2tzIjoxMH0.L0uyMcBzvzzK6c1hisfXuL0Cp-Gbwu6qiSMVq0Ojwaw" + token := "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjYsIkxpbWl0RGF5VGFza3MiOjEwfQ.p71B7Pd2SydLxeVm5LqZIQXgljHAOJN9z5we4wlW2lA" req.Header.Set("Authorization", token) res := executeRequest(req) checkResponseCode(t, http.StatusOK, res.Code) - var r response json.Unmarshal(res.Body.Bytes(), &r) // json.NewDecoder(res.Body).Decode(&m) - if r.Status != "Success" { - t.Errorf("Expected Status field to be 'Success' but. Got '%v'", r.Status) - } - if r.Message != "Success" { - t.Errorf("Expected Message field to be 'Success'. Got '%v'", r.Message) - } - - if r.Data["email"] != "test2@example.com" { + if r.Data["email"] != "user1@gmail.com" { t.Errorf("Expected field of Data email to be 'test2@example.com'. Got '%v'", r.Data["email"]) } - if r.Data["name"] != "test user1" { - t.Errorf("Expected field of Data name to be 'test user'. Got '%v'", r.Data["name"]) + if r.Data["name"] != "user1" { + t.Errorf("Expected field of Data name to be 'user1'. Got '%v'", r.Data["name"]) } if r.Data["is_payment"] != false { t.Errorf("Expected field of Data is_payment to be 'true'. Got '%v'", r.Data["is_payment"]) @@ -106,7 +83,10 @@ func TestGetMe(t *testing.T) { if r.Data["limit_day_tasks"] != 10.0 { t.Errorf("Expected field of Data limit_day_tasks to be '10'. Got '%v'", r.Data["limit_day_tasks"]) } + t.Logf("Response Message: %v", r.Message) } + +// Pass ✅ func TestUpdateMe(t *testing.T) { payload := []byte(`{ "name": "updated test user", @@ -114,22 +94,14 @@ func TestUpdateMe(t *testing.T) { "password": "123456" }`) req, _ := http.NewRequest("PATCH", "/api/users/edit", bytes.NewBuffer(payload)) - token := "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjIwLCJMaW1pdERheVRhc2tzIjoxMH0.8UHK64DHDcly-fSB22HdsVAoh5y_P_YhN-eX_SZoO_w" + token := "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjExLCJMaW1pdERheVRhc2tzIjoxMH0.0C6oI6DMy6lKnlw8o_VOllwGz7qkGC-955rVDgzEak4" req.Header.Set("Authorization", token) res := executeRequest(req) checkResponseCode(t, http.StatusOK, res.Code) - var r response json.Unmarshal(res.Body.Bytes(), &r) // json.NewDecoder(res.Body).Decode(&m) - if r.Status != "Success" { - t.Errorf("Expected Status field to be 'Success' but. Got '%v'", r.Status) - } - - if r.Message != "Success update your account!" { - t.Errorf("Expected Message field to be 'Success'. Got '%v'", r.Message) - } if r.Data["email"] != "updatedtest@example.com" { t.Errorf("Expected field of Data email to be 'updatedtest@example.com'. Got '%v'", r.Data["email"]) @@ -138,31 +110,24 @@ func TestUpdateMe(t *testing.T) { if r.Data["name"] != "updated test user" { t.Errorf("Expected field of Data name to be 'updated test user'. Got '%v'", r.Data["name"]) } + t.Logf("Response Message: %v", r.Message) } +// Pass ✅ func TestDeleteMe(t *testing.T) { payload := []byte(`{ "password": "123456" }`) req, _ := http.NewRequest("DELETE", "/api/users/delete", bytes.NewBuffer(payload)) - token := "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjIzLCJMaW1pdERheVRhc2tzIjoxMH0.L0uyMcBzvzzK6c1hisfXuL0Cp-Gbwu6qiSMVq0Ojwaw" + token := "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjYsIkxpbWl0RGF5VGFza3MiOjEwfQ.p71B7Pd2SydLxeVm5LqZIQXgljHAOJN9z5we4wlW2lA" req.Header.Set("Authorization", token) res := executeRequest(req) checkResponseCode(t, http.StatusNoContent, res.Code) - var r response json.Unmarshal(res.Body.Bytes(), &r) - // json.NewDecoder(res.Body).Decode(&m) - if r.Status != "Success" { - t.Errorf("Expected Status field to be 'Success' but. Got '%v'", r.Status) - } - - if r.Message != "Success delete your account!" { - t.Errorf("Expected Message field to be 'Success'. Got '%v'", r.Message) - } - if r.Data != nil { t.Errorf("Expected field of Data to be 'nil'. Got '%v'", r.Data) } + t.Logf("Response Message: %v", r.Message) } diff --git a/main.go b/main.go index 7b778e03a..394ec4c9f 100644 --- a/main.go +++ b/main.go @@ -5,7 +5,6 @@ import ( "syscall" "github.com/joho/godotenv" - _ "github.com/lib/pq" "github.com/manabie-com/togo/app" ) diff --git a/models/task.go b/models/task.go index bfa17f107..54df1b9ef 100644 --- a/models/task.go +++ b/models/task.go @@ -13,7 +13,7 @@ type Task struct { UserId uint32 `json:"userId"` } -func (t *Task) InsertOne(db *sql.DB) error { +func (t *Task) InsertTask(db *sql.DB) error { err := db.QueryRow(`INSERT INTO tasks(name, content, user_id) VALUES($3, $2, $1) RETURNING name, content, created_at`, t.UserId, t.Content, t.Name).Scan(&t.Name, &t.Content, &t.CreatedAt) if err != nil { return err @@ -21,8 +21,48 @@ func (t *Task) InsertOne(db *sql.DB) error { return nil } -func (t *Task) GetOneByUserId(db *sql.DB) error { - err := db.QueryRow(`UPDATE tasks SET name = $3 WHERE id = $1 AND user_id = $2 RETURNING name, content, created_at`, t.ID, t.UserId, t.Name).Scan(&t.Name, &t.Content, &t.CreatedAt) +func (t *Task) GetTaskByUserId(db *sql.DB) error { + err := db.QueryRow(`SELECT name, content, created_at FROM tasks WHERE id = $1 AND user_id = $2 `, t.ID, t.UserId).Scan(&t.Name, &t.Content, &t.CreatedAt) + if err != nil { + return err + } + return nil +} + +func (t *Task) GetTasksByUserId(db *sql.DB) ([]*Task, error) { + var tasks []*Task + rows, err := db.Query(`SELECT * FROM tasks WHERE user_id = $1`, t.UserId) + if err != nil { + return tasks, err + } + + for rows.Next() { + var task = &Task{} + rows.Scan(&task.ID, &task.Name, &task.Content, &task.CreatedAt, &task.UserId) + tasks = append(tasks, task) + } + return tasks, nil +} + +func (t *Task) IsLimit(db *sql.DB, todayTasksLimit uint) bool { + // check limit day tasks + var tasksLength uint + _ = db.QueryRow(`SELECT COUNT(id) + FROM tasks + WHERE created_at >= NOW() - INTERVAL '24 HOURS' AND user_id = $1`, t.UserId).Scan(&tasksLength) + return tasksLength == todayTasksLimit +} + +func (t *Task) DeleteTaskById(db *sql.DB, id string) error { + _, err := db.Exec(`DELETE FROM tasks WHERE id = $1 AND user_id = $2`, id, t.UserId) + if err != nil { + return err + } + return nil +} + +func (t *Task) UpdateTaskById(db *sql.DB, id string) error { + _, err := db.Exec(`UPDATE tasks SET name = $3 WHERE id = $1 AND user_id = $2 RETURNING name, content, created_at`, id, t.UserId, t.Name) if err != nil { return err } diff --git a/models/user.go b/models/user.go index 9e70fb62a..9c706562a 100644 --- a/models/user.go +++ b/models/user.go @@ -6,7 +6,7 @@ import ( type User struct { ID uint32 `json:"id" validate:"omitempty"` - Email string `json:"email" validate:"required,email"` + Email string `json:"email" validate:"required,email,min=10,max=30"` Name string `json:"name" validate:"required,min=5,max=20"` Password string `json:"password" validate:"required,min=6,max=20"` IsPayment bool `json:"isPayment" validate:"omitempty"` @@ -14,7 +14,7 @@ type User struct { LimitDayTasks uint `json:"limitDayTasks" validate:"omitempty"` } -func (u *User) InsertOne(db *sql.DB) error { +func (u *User) InsertUser(db *sql.DB) error { err := db.QueryRow(`INSERT INTO users(name, email, password) VALUES($1, $2, $3) RETURNING id, name, email`, u.Name, u.Email, u.Password).Scan(&u.ID, &u.Name, &u.Email) if err != nil { return err @@ -22,7 +22,7 @@ func (u *User) InsertOne(db *sql.DB) error { return nil } -func (u *User) GetOneById(db *sql.DB) error { +func (u *User) GetUserById(db *sql.DB) error { err := db.QueryRow(`SELECT id, name, email, password, is_payment, is_active, limit_day_tasks FROM users WHERE id = $1`, u.ID).Scan(&u.ID, &u.Name, &u.Email, &u.Password, &u.IsPayment, &u.IsActive, &u.LimitDayTasks) if err != nil { return err @@ -30,10 +30,43 @@ func (u *User) GetOneById(db *sql.DB) error { return nil } -func (u *User) GetOneByEmail(db *sql.DB) error { +func (u *User) GetUserByEmail(db *sql.DB) error { err := db.QueryRow(`SELECT id, name, email, password, is_payment, is_active, limit_day_tasks FROM users WHERE email = $1`, u.Email).Scan(&u.ID, &u.Name, &u.Email, &u.Password, &u.IsPayment, &u.IsActive, &u.LimitDayTasks) if err != nil { return err } return nil } + +func (u *User) ActiveUser(db *sql.DB) error { + _, err := db.Exec(`UPDATE users SET is_active = $1 WHERE id = $2`, true, u.ID) + if err != nil { + return err + } + return nil +} + +func (u *User) UpdateUser(db *sql.DB) error { + _, err := db.Exec(`UPDATE users SET name = $1, email = $2 WHERE id = $3`, u.Name, u.Email, u.ID) + if err != nil { + return err + } + return nil +} + +// DeleteUser disable user active status => set is_active to false +func (u *User) DeleteUser(db *sql.DB) error { + _, err := db.Exec(`UPDATE users SET is_active = $1 WHERE id = $2`, u.IsActive, u.ID) + if err != nil { + return err + } + return nil +} + +func (u *User) UpgradePremium(db *sql.DB) error { + err := db.QueryRow(`UPDATE users SET is_payment = $1, limit_day_tasks = $2 WHERE id = $3 RETURNING name, email`, u.IsPayment, u.LimitDayTasks, u.ID).Scan(&u.Name, &u.Email) + if err != nil { + return err + } + return nil +} diff --git a/utils/utils.go b/utils/utils.go index fd977b911..6b0f4846b 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -11,12 +11,21 @@ type response struct { Data interface{} } -func Respond(w http.ResponseWriter, statusCode int, status, message string, data interface{}) { +func SuccessRespond(w http.ResponseWriter, statusCode int, message string, data interface{}) { res := response{ - Status: status, + Status: "Success", Message: message, Data: data, } w.WriteHeader(statusCode) json.NewEncoder(w).Encode(res) } +func FailureRespond(w http.ResponseWriter, statusCode int, message string) { + res := response{ + Status: "Failure", + Message: message, + Data: nil, + } + w.WriteHeader(statusCode) + json.NewEncoder(w).Encode(res) +} From ed40cbe6e43e6e755dd36e07bfe4c10f5a4c3be6 Mon Sep 17 00:00:00 2001 From: Quang Ha Date: Tue, 28 Jun 2022 17:15:32 +0700 Subject: [PATCH 15/41] divide app file to 3 components (file) --- app/app.go | 71 ------------------------------------------------- app/handlers.go | 51 +++++++++++++++++++++++++++++++++++ app/routes.go | 31 +++++++++++++++++++++ 3 files changed, 82 insertions(+), 71 deletions(-) create mode 100644 app/handlers.go create mode 100644 app/routes.go diff --git a/app/app.go b/app/app.go index f9f02275f..63764e174 100644 --- a/app/app.go +++ b/app/app.go @@ -10,8 +10,6 @@ import ( "github.com/gorilla/mux" "github.com/joho/godotenv" _ "github.com/lib/pq" - "github.com/manabie-com/togo/controllers" - c "github.com/manabie-com/togo/controllers" ) type App struct { @@ -38,75 +36,6 @@ func (a *App) Init() { a.Routes() } -func (a *App) Routes() { - - router := a.Router - // sub router like http://:/api/users - userRouter := router.PathPrefix("/api/users").Subrouter() - userRouter.HandleFunc("/me", a.GetMe).Methods("GET") - userRouter.HandleFunc("/signup", a.SignUp).Methods("POST") - userRouter.HandleFunc("/login", a.Login).Methods("POST") - userRouter.HandleFunc("/edit", a.UpdateMe).Methods("PATCH") - userRouter.HandleFunc("/delete", a.DeleteMe).Methods("DELETE") - // sub router like http://:/api/tasks - taskRouter := router.PathPrefix("/api/tasks").Subrouter() - taskRouter.HandleFunc("", a.GetTasks).Methods("GET") - taskRouter.HandleFunc("/{id}", a.GetTask).Methods("GET") - taskRouter.HandleFunc("/add", a.Add).Methods("POST") - taskRouter.HandleFunc("/{id}", a.Edit).Methods("PATCH") - taskRouter.HandleFunc("/{id}", a.Delete).Methods("DELETE") - // sub routes like http://:/api/payments - paymentRouter := router.PathPrefix("/api/payments").Subrouter() - paymentRouter.HandleFunc("", a.Payment).Methods("POST") - // runs database - router.Use(mux.CORSMethodMiddleware(router)) - router.Use(controllers.JwtAuthentication) -} - -func (a *App) GetMe(w http.ResponseWriter, r *http.Request) { - c.GetMe(a.DB, w, r) -} - -func (a *App) SignUp(w http.ResponseWriter, r *http.Request) { - c.SignUp(a.DB, w, r) -} - -func (a *App) Login(w http.ResponseWriter, r *http.Request) { - c.Login(a.DB, w, r) -} - -func (a *App) UpdateMe(w http.ResponseWriter, r *http.Request) { - c.UpdateMe(a.DB, w, r) -} - -func (a *App) DeleteMe(w http.ResponseWriter, r *http.Request) { - c.DeleteMe(a.DB, w, r) -} - -func (a *App) GetTasks(w http.ResponseWriter, r *http.Request) { - c.GetTasks(a.DB, w, r) -} - -func (a *App) GetTask(w http.ResponseWriter, r *http.Request) { - c.GetTask(a.DB, w, r) -} - -func (a *App) Add(w http.ResponseWriter, r *http.Request) { - c.Add(a.DB, w, r) -} - -func (a *App) Edit(w http.ResponseWriter, r *http.Request) { - c.Edit(a.DB, w, r) -} - -func (a *App) Delete(w http.ResponseWriter, r *http.Request) { - c.Delete(a.DB, w, r) -} - -func (a *App) Payment(w http.ResponseWriter, r *http.Request) { - c.Payment(a.DB, w, r) -} - func (a *App) Run(host string) { log.Fatal(http.ListenAndServe(host, a.Router)) } diff --git a/app/handlers.go b/app/handlers.go new file mode 100644 index 000000000..ad61b13b0 --- /dev/null +++ b/app/handlers.go @@ -0,0 +1,51 @@ +package app + +import ( + "net/http" + + c "github.com/manabie-com/togo/controllers" +) + +func (a *App) GetMe(w http.ResponseWriter, r *http.Request) { + c.GetMe(a.DB, w, r) +} + +func (a *App) SignUp(w http.ResponseWriter, r *http.Request) { + c.SignUp(a.DB, w, r) +} + +func (a *App) Login(w http.ResponseWriter, r *http.Request) { + c.Login(a.DB, w, r) +} + +func (a *App) UpdateMe(w http.ResponseWriter, r *http.Request) { + c.UpdateMe(a.DB, w, r) +} + +func (a *App) DeleteMe(w http.ResponseWriter, r *http.Request) { + c.DeleteMe(a.DB, w, r) +} + +func (a *App) GetTasks(w http.ResponseWriter, r *http.Request) { + c.GetTasks(a.DB, w, r) +} + +func (a *App) GetTask(w http.ResponseWriter, r *http.Request) { + c.GetTask(a.DB, w, r) +} + +func (a *App) Add(w http.ResponseWriter, r *http.Request) { + c.Add(a.DB, w, r) +} + +func (a *App) Edit(w http.ResponseWriter, r *http.Request) { + c.Edit(a.DB, w, r) +} + +func (a *App) Delete(w http.ResponseWriter, r *http.Request) { + c.Delete(a.DB, w, r) +} + +func (a *App) Payment(w http.ResponseWriter, r *http.Request) { + c.Payment(a.DB, w, r) +} diff --git a/app/routes.go b/app/routes.go new file mode 100644 index 000000000..00c368f8e --- /dev/null +++ b/app/routes.go @@ -0,0 +1,31 @@ +package app + +import ( + "github.com/gorilla/mux" + "github.com/manabie-com/togo/controllers" +) + +func (a *App) Routes() { + + router := a.Router + // sub router like http://:/api/users + userRouter := router.PathPrefix("/api/users").Subrouter() + userRouter.HandleFunc("/me", a.GetMe).Methods("GET") + userRouter.HandleFunc("/signup", a.SignUp).Methods("POST") + userRouter.HandleFunc("/login", a.Login).Methods("POST") + userRouter.HandleFunc("/edit", a.UpdateMe).Methods("PATCH") + userRouter.HandleFunc("/delete", a.DeleteMe).Methods("DELETE") + // sub router like http://:/api/tasks + taskRouter := router.PathPrefix("/api/tasks").Subrouter() + taskRouter.HandleFunc("", a.GetTasks).Methods("GET") + taskRouter.HandleFunc("/{id}", a.GetTask).Methods("GET") + taskRouter.HandleFunc("/add", a.Add).Methods("POST") + taskRouter.HandleFunc("/{id}", a.Edit).Methods("PATCH") + taskRouter.HandleFunc("/{id}", a.Delete).Methods("DELETE") + // sub routes like http://:/api/payments + paymentRouter := router.PathPrefix("/api/payments").Subrouter() + paymentRouter.HandleFunc("", a.Payment).Methods("POST") + // runs database + router.Use(mux.CORSMethodMiddleware(router)) + router.Use(controllers.JwtAuthentication) +} From b18a1b93db9bec8c0ecec882720f9ac9d9e95f99 Mon Sep 17 00:00:00 2001 From: Quang Ha Date: Wed, 29 Jun 2022 23:57:09 +0700 Subject: [PATCH 16/41] udpate testing --- controllers/controllers_main_test.go | 39 ++++++++-- controllers/payment_test.go | 14 ++-- controllers/task.go | 3 - controllers/task_test.go | 86 ++++++++++----------- controllers/user_test.go | 110 ++++++++++++++++----------- 5 files changed, 151 insertions(+), 101 deletions(-) diff --git a/controllers/controllers_main_test.go b/controllers/controllers_main_test.go index dc17df23c..df907f770 100644 --- a/controllers/controllers_main_test.go +++ b/controllers/controllers_main_test.go @@ -10,17 +10,18 @@ import ( "github.com/manabie-com/togo/app" ) -var ( - a app.App - r response -) - type response struct { Status string Message string Data map[string]interface{} } +var ( + a app.App + r response + token string = "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjUyLCJMaW1pdERheVRhc2tzIjoxMH0.PcgDnM8-LY0oyNiSfYRyQVIMryU1TfqWgEgq6geqEXc" +) + func TestMain(m *testing.M) { a = app.App{} a.Init() @@ -39,3 +40,31 @@ func checkResponseCode(t *testing.T, expected, actual int) { t.Errorf("Expected response code %d. Got %d\n", expected, actual) } } + +func checkResponseStatus(t *testing.T, expected, actual string) { + if expected != actual { + t.Errorf("Expected response Status %s. Got %s\n", expected, actual) + } +} + +func checkResponseMessage(t *testing.T, expected, actual string) { + if expected != actual { + t.Errorf("Expected response Message %s. Got %s\n", expected, actual) + } +} + +func rollbackUser() error { + _, err := a.DB.Exec(`DELETE FROM users WHERE email = $1`, "test_user@gmail.com") + if err != nil { + return err + } + return nil +} + +func rollbackPayment(email interface{}) error { + _, err := a.DB.Exec(`UPDATE users SET is_payment = $1, limit_day_tasks = $2 WHERE email = $3`, false, 10, email) + if err != nil { + return err + } + return nil +} diff --git a/controllers/payment_test.go b/controllers/payment_test.go index 970dec6d0..a0d300303 100644 --- a/controllers/payment_test.go +++ b/controllers/payment_test.go @@ -9,17 +9,19 @@ import ( // Pass ✅ func TestPayment(t *testing.T) { req, _ := http.NewRequest("POST", "/api/payments", nil) - token := "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjYsIkxpbWl0RGF5VGFza3MiOjEwfQ.p71B7Pd2SydLxeVm5LqZIQXgljHAOJN9z5we4wlW2lA" req.Header.Set("Authorization", token) res := executeRequest(req) json.Unmarshal(res.Body.Bytes(), &r) checkResponseCode(t, http.StatusOK, res.Code) - if r.Data["name"] != "user1" { - t.Errorf("Expected user name is 'user1'. Got '%v'", r.Data["name"]) + checkResponseStatus(t, "Success", r.Status) + checkResponseMessage(t, "Success upgrade Premium account. Please login again to try new upgrade", r.Message) + + if r.Data["name"] != "testinguser" { + t.Errorf("Expected user name is 'testinguser'. Got '%v'", r.Data["name"]) } - if r.Data["email"] != "user1@gmail.com" { - t.Errorf("Expected user email is 'user1@gmail.com'. Got '%v'", r.Data["email"]) + if r.Data["email"] != "testinguser@gmail.com" { + t.Errorf("Expected user email is 'testinguser@gmail.com'. Got '%v'", r.Data["email"]) } if r.Data["is_payment"] != true { t.Errorf("Expected user is_payment field is 'true'. Got '%v'", r.Data["is_payment"]) @@ -27,4 +29,6 @@ func TestPayment(t *testing.T) { if r.Data["limit_day_tasks"] != 20.0 { t.Errorf("Expected user limit task field is '20'. Got '%v'", r.Data["limit_day_tasks"]) } + // rollback before Payments + rollbackPayment(r.Data["email"]) } diff --git a/controllers/task.go b/controllers/task.go index 67c8b36cd..c4aaea57d 100644 --- a/controllers/task.go +++ b/controllers/task.go @@ -3,7 +3,6 @@ package controllers import ( "database/sql" "encoding/json" - "fmt" "net/http" "strconv" @@ -110,7 +109,6 @@ var Edit = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { name string = task.Name content string = task.Content ) - fmt.Println(task) if name != "" { task.Name = name } @@ -122,7 +120,6 @@ var Edit = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { u.FailureRespond(w, http.StatusBadRequest, err.Error()) return } - fmt.Println(task) u.SuccessRespond(w, http.StatusOK, "Success update task", map[string]interface{}{ "name": task.Name, "content": task.Content, diff --git a/controllers/task_test.go b/controllers/task_test.go index aa354fb9d..d34bd71b2 100644 --- a/controllers/task_test.go +++ b/controllers/task_test.go @@ -7,22 +7,47 @@ import ( "testing" ) +// Pass ✅ +func TestAdd(t *testing.T) { + payload := []byte(`{ + "name" : "task name", + "content" : "task content" + }`) + + req, _ := http.NewRequest("POST", "/api/tasks/add", bytes.NewBuffer(payload)) + req.Header.Set("Authorization", token) + res := executeRequest(req) + json.Unmarshal(res.Body.Bytes(), &r) + + checkResponseCode(t, http.StatusCreated, res.Code) + checkResponseStatus(t, "Success", r.Status) + checkResponseMessage(t, "Success create task", r.Message) + + if r.Data["name"] != "task name" { + t.Errorf("Expected task name is 'task name'. Got '%v'", r.Data["name"]) + } + if r.Data["content"] != "task content" { + t.Errorf("Expected task content is 'task content'. Got '%v'", r.Data["content"]) + } +} + // Pass ✅ func TestGetTasks(t *testing.T) { - type tasksRes struct { + type tasksResponse struct { Status string Message string Data []map[string]interface{} } // get user id here req, _ := http.NewRequest("GET", "/api/tasks", nil) - token := "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjYsIkxpbWl0RGF5VGFza3MiOjEwfQ.p71B7Pd2SydLxeVm5LqZIQXgljHAOJN9z5we4wlW2lA" req.Header.Set("Authorization", token) res := executeRequest(req) - checkResponseCode(t, http.StatusOK, res.Code) - var r tasksRes - json.Unmarshal(res.Body.Bytes(), &r) + var tr tasksResponse + json.Unmarshal(res.Body.Bytes(), &tr) + checkResponseCode(t, http.StatusOK, res.Code) + checkResponseStatus(t, "Success", r.Status) + checkResponseMessage(t, "Success", r.Message) // json.NewDecoder(res.Body).Decode(&m) if r.Status != "Success" { t.Errorf("Expected Status field to be 'Success' but. Got '%v'", r.Status) @@ -31,56 +56,26 @@ func TestGetTasks(t *testing.T) { if len(r.Data) != 8 { t.Errorf("Expected tasks length to be '7'. Got '%v'", len(r.Data)) } - - for _, val := range r.Data { - if val["id"] == 4 { - if val["name"] != "task name" { - t.Errorf("Expected type of Data to be 'task name' value. Got '%v'", val["name"]) - } - if val["content"] != "subtask1 \n subtask2" { - t.Errorf("Expected type of Data to be 'subtask1 \n subtask2' value. Got '%v'", val["content"]) - } - } - } } // Pass ✅ func TestGetTask(t *testing.T) { req, _ := http.NewRequest("GET", "/api/tasks/4", nil) // auth token - token := "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjYsIkxpbWl0RGF5VGFza3MiOjEwfQ.p71B7Pd2SydLxeVm5LqZIQXgljHAOJN9z5we4wlW2lA" req.Header.Set("Authorization", token) res := executeRequest(req) json.Unmarshal(res.Body.Bytes(), &r) - checkResponseCode(t, http.StatusOK, res.Code) - if r.Data["name"] != "task name" { - t.Errorf("Expected type of Data to be 'task name' value. Got '%v'", r.Data["name"]) - } - if r.Data["content"] != "subtask1 \n subtask2" { - t.Errorf("Expected type of Data to be 'subtask1 \n subtask2' value. Got '%v'", r.Data["content"]) - } -} - -// Pass ✅ -func TestAdd(t *testing.T) { - payload := []byte(`{ - "name" : "task name", - "content" : "task content" - }`) - req, _ := http.NewRequest("POST", "/api/tasks/add", bytes.NewBuffer(payload)) - token := "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjYsIkxpbWl0RGF5VGFza3MiOjEwfQ.p71B7Pd2SydLxeVm5LqZIQXgljHAOJN9z5we4wlW2lA" - req.Header.Set("Authorization", token) - res := executeRequest(req) - json.Unmarshal(res.Body.Bytes(), &r) - checkResponseCode(t, http.StatusCreated, res.Code) + checkResponseCode(t, http.StatusOK, res.Code) + checkResponseStatus(t, "Success", r.Status) + checkResponseMessage(t, "Success", r.Message) if r.Data["name"] != "task name" { - t.Errorf("Expected task name is 'task name'. Got '%v'", r.Data["name"]) + t.Errorf("Expected task name to be 'task name' value. Got '%v'", r.Data["name"]) } - if r.Data["content"] != "task content" { - t.Errorf("Expected task content is 'task content'. Got '%v'", r.Data["content"]) + if r.Data["content"] != "subtask1 \n subtask2" { + t.Errorf("Expected task content to be 'subtask1 \n subtask2' value. Got '%v'", r.Data["content"]) } } @@ -91,12 +86,14 @@ func TestEdit(t *testing.T) { "content" : "task content" }`) req, _ := http.NewRequest("PATCH", "/api/tasks/4", bytes.NewBuffer(payload)) - token := "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjYsIkxpbWl0RGF5VGFza3MiOjEwfQ.p71B7Pd2SydLxeVm5LqZIQXgljHAOJN9z5we4wlW2lA" req.Header.Set("Authorization", token) res := executeRequest(req) json.Unmarshal(res.Body.Bytes(), &r) + checkResponseCode(t, http.StatusOK, res.Code) + checkResponseStatus(t, "Success", r.Status) + checkResponseMessage(t, "Success update task", r.Message) if r.Data["name"] != "task name" { t.Errorf("Expected task name is 'task name'. Got '%v'", r.Data["name"]) } @@ -108,12 +105,15 @@ func TestEdit(t *testing.T) { // Pass ✅ func TestDelete(t *testing.T) { req, _ := http.NewRequest("DELETE", "/api/tasks/3", nil) - token := "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjYsIkxpbWl0RGF5VGFza3MiOjEwfQ.p71B7Pd2SydLxeVm5LqZIQXgljHAOJN9z5we4wlW2lA" req.Header.Set("Authorization", token) res := executeRequest(req) json.Unmarshal(res.Body.Bytes(), &r) + checkResponseCode(t, http.StatusNoContent, res.Code) + checkResponseStatus(t, "Success", r.Status) + checkResponseMessage(t, "Success delete task", r.Message) + if r.Data != nil { t.Errorf("Expected type of Data to be 'nil' value. Got '%v'", r.Data) } diff --git a/controllers/user_test.go b/controllers/user_test.go index b701c2e51..32f4c69f7 100644 --- a/controllers/user_test.go +++ b/controllers/user_test.go @@ -10,124 +10,144 @@ import ( // Pass ✅ func TestSignUp(t *testing.T) { payload := []byte(`{ - "name": "user1", - "email": "user10@gmail.com", + "name": "test_user", + "email": "test_user@gmail.com", "password": "123456" }`) req, _ := http.NewRequest("POST", "/api/users/signup", bytes.NewBuffer(payload)) req.Header.Set("Content-Type", "application/json") res := executeRequest(req) - checkResponseCode(t, http.StatusCreated, res.Code) - - var r response json.Unmarshal(res.Body.Bytes(), &r) + checkResponseCode(t, http.StatusCreated, res.Code) + checkResponseStatus(t, "Success", r.Status) + checkResponseMessage(t, "Created Account", r.Message) - if r.Data["email"] != "user10@gmail.com" { - t.Errorf("Expected type of Data to be 'user10@gmail.com'. Got '%v'", r.Data["email"]) + if r.Data["email"] != "test_user@gmail.com" { + t.Errorf("Expected type of Data to be 'test_user@gmail.com'. Got '%v'", r.Data["email"]) } - if r.Data["name"] != "user1" { - t.Errorf("Expected type of Data to be 'user1'. Got '%v'", r.Data["name"]) + if r.Data["name"] != "test_user" { + t.Errorf("Expected type of Data to be 'test_user'. Got '%v'", r.Data["name"]) } - t.Logf("Response Message: %v", r.Message) } // Pass ✅ func TestLogin(t *testing.T) { payload := []byte(`{ - "email": "user1@gmail.com", + "email": "test_user@gmail.com", "password": "123456" }`) req, _ := http.NewRequest("POST", "/api/users/login", bytes.NewBuffer(payload)) req.Header.Set("Content-Type", "application/json") res := executeRequest(req) - checkResponseCode(t, http.StatusOK, res.Code) - var r response json.Unmarshal(res.Body.Bytes(), &r) - if r.Data["email"] != "user1@gmail.com" { - t.Errorf("Expected type of Data to be 'user1@gmail.com'. Got '%v'", r.Data["email"]) + checkResponseCode(t, http.StatusOK, res.Code) + checkResponseStatus(t, "Success", r.Status) + checkResponseMessage(t, "Login Success", r.Message) + + if r.Data["email"] != "test_user@gmail.com" { + t.Errorf("Expected type of Data to be 'test_user@gmail.com'. Got '%v'", r.Data["email"]) } - if r.Data["name"] != "user1" { - t.Errorf("Expected type of Data to be 'user1'. Got '%v'", r.Data["name"]) + if r.Data["name"] != "test_user" { + t.Errorf("Expected type of Data to be 'test_user'. Got '%v'", r.Data["name"]) } - t.Logf("Response Message: %v", r.Message) - // the id is compared to 1.0 because JSON unmarshaling converts numbers to - // floats, when the target is a map[string]interface{} } // Pass ✅ func TestGetMe(t *testing.T) { + // get token from test user + token := getToken() req, _ := http.NewRequest("GET", "/api/users/me", nil) - token := "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjYsIkxpbWl0RGF5VGFza3MiOjEwfQ.p71B7Pd2SydLxeVm5LqZIQXgljHAOJN9z5we4wlW2lA" req.Header.Set("Authorization", token) res := executeRequest(req) - checkResponseCode(t, http.StatusOK, res.Code) - json.Unmarshal(res.Body.Bytes(), &r) + checkResponseCode(t, http.StatusOK, res.Code) + checkResponseStatus(t, "Success", r.Status) + checkResponseMessage(t, "Success", r.Message) + // json.NewDecoder(res.Body).Decode(&m) - if r.Data["email"] != "user1@gmail.com" { - t.Errorf("Expected field of Data email to be 'test2@example.com'. Got '%v'", r.Data["email"]) + if r.Data["name"] != "test_user" { + t.Errorf("Expected user name is 'test_user'. Got '%v'", r.Data["name"]) } - - if r.Data["name"] != "user1" { - t.Errorf("Expected field of Data name to be 'user1'. Got '%v'", r.Data["name"]) + if r.Data["email"] != "test_user@gmail.com" { + t.Errorf("Expected user email is 'test_user@gmail.com'. Got '%v'", r.Data["email"]) } if r.Data["is_payment"] != false { - t.Errorf("Expected field of Data is_payment to be 'true'. Got '%v'", r.Data["is_payment"]) + t.Errorf("Expected user is_payment field is 'true'. Got '%v'", r.Data["is_payment"]) } if r.Data["limit_day_tasks"] != 10.0 { - t.Errorf("Expected field of Data limit_day_tasks to be '10'. Got '%v'", r.Data["limit_day_tasks"]) + t.Errorf("Expected user limit task field is '10'. Got '%v'", r.Data["limit_day_tasks"]) } - t.Logf("Response Message: %v", r.Message) } // Pass ✅ func TestUpdateMe(t *testing.T) { + // get token from test user + token := getToken() payload := []byte(`{ - "name": "updated test user", - "email": "updatedtest@example.com", + "name": "updated_test_user", "password": "123456" }`) req, _ := http.NewRequest("PATCH", "/api/users/edit", bytes.NewBuffer(payload)) - token := "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjExLCJMaW1pdERheVRhc2tzIjoxMH0.0C6oI6DMy6lKnlw8o_VOllwGz7qkGC-955rVDgzEak4" req.Header.Set("Authorization", token) res := executeRequest(req) - checkResponseCode(t, http.StatusOK, res.Code) - json.Unmarshal(res.Body.Bytes(), &r) + checkResponseCode(t, http.StatusOK, res.Code) + checkResponseStatus(t, "Success", r.Status) + checkResponseMessage(t, "Success update your account!", r.Message) + // json.NewDecoder(res.Body).Decode(&m) - if r.Data["email"] != "updatedtest@example.com" { - t.Errorf("Expected field of Data email to be 'updatedtest@example.com'. Got '%v'", r.Data["email"]) + if r.Data["email"] != "test_user@gmail.com" { + t.Errorf("Expected field of Data email to be 'test_user@gmail.com'. Got '%v'", r.Data["email"]) } - if r.Data["name"] != "updated test user" { - t.Errorf("Expected field of Data name to be 'updated test user'. Got '%v'", r.Data["name"]) + if r.Data["name"] != "updated_test_user" { + t.Errorf("Expected field of Data name to be 'updated_test_user'. Got '%v'", r.Data["name"]) } - t.Logf("Response Message: %v", r.Message) } // Pass ✅ func TestDeleteMe(t *testing.T) { + token := getToken() payload := []byte(`{ "password": "123456" }`) req, _ := http.NewRequest("DELETE", "/api/users/delete", bytes.NewBuffer(payload)) - token := "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjYsIkxpbWl0RGF5VGFza3MiOjEwfQ.p71B7Pd2SydLxeVm5LqZIQXgljHAOJN9z5we4wlW2lA" req.Header.Set("Authorization", token) res := executeRequest(req) - checkResponseCode(t, http.StatusNoContent, res.Code) - json.Unmarshal(res.Body.Bytes(), &r) + checkResponseCode(t, http.StatusNoContent, res.Code) + checkResponseStatus(t, "Success", r.Status) + checkResponseMessage(t, "Success delete your account!", r.Message) + if r.Data != nil { t.Errorf("Expected field of Data to be 'nil'. Got '%v'", r.Data) } - t.Logf("Response Message: %v", r.Message) + rollbackUser() +} + +func getToken() string { + type predictResponse struct { + Status string + Message string + Data map[string]string + } + payload := []byte(`{ + "email": "test_user@gmail.com", + "password": "123456" + }`) + req, _ := http.NewRequest("POST", "/api/users/login", bytes.NewBuffer(payload)) + req.Header.Set("Content-Type", "application/json") + res := executeRequest(req) + var pr predictResponse + json.Unmarshal(res.Body.Bytes(), &pr) + return "Bearer " + pr.Data["token"] } From 41332fc28865119bdcb719d46f0de1f58dda72ec Mon Sep 17 00:00:00 2001 From: Quang Ha Date: Thu, 30 Jun 2022 11:23:34 +0700 Subject: [PATCH 17/41] update testing functions and add rollback features --- controllers/controllers_main_test.go | 8 +++ controllers/task.go | 65 +++++++++++++-------- controllers/task_test.go | 86 +++++++++++++++++++++------- controllers/user_test.go | 2 + 4 files changed, 117 insertions(+), 44 deletions(-) diff --git a/controllers/controllers_main_test.go b/controllers/controllers_main_test.go index df907f770..946f2bc26 100644 --- a/controllers/controllers_main_test.go +++ b/controllers/controllers_main_test.go @@ -61,6 +61,14 @@ func rollbackUser() error { return nil } +func rollbackTask() error { + _, err := a.DB.Exec(`DELETE FROM tasks WHERE user_id = (SELECT users.id FROM users where email = $1)`, "test_user@gmail.com") + if err != nil { + return err + } + return nil +} + func rollbackPayment(email interface{}) error { _, err := a.DB.Exec(`UPDATE users SET is_payment = $1, limit_day_tasks = $2 WHERE email = $3`, false, 10, email) if err != nil { diff --git a/controllers/task.go b/controllers/task.go index c4aaea57d..89c9e06e7 100644 --- a/controllers/task.go +++ b/controllers/task.go @@ -3,8 +3,8 @@ package controllers import ( "database/sql" "encoding/json" + "fmt" "net/http" - "strconv" "github.com/go-playground/validator/v10" "github.com/gorilla/mux" @@ -20,7 +20,7 @@ var GetTasks = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { } tasks, err := task.GetTasksByUserId(db) if err != nil { - u.FailureRespond(w, http.StatusNotFound, err.Error()) + u.SuccessRespond(w, http.StatusOK, "Success", nil) return } //Everything OK @@ -30,22 +30,20 @@ var GetTasks = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { var GetTask = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { // decode token from middleware decoded := r.Context().Value("user").(*models.Token) - // convert id params string -> uint32 - id := func(id string) uint32 { - u64, err := strconv.ParseUint(id, 10, 32) - if err != nil { - u.FailureRespond(w, http.StatusBadRequest, "ID must be a number type.") - } - return uint32(u64) - }(mux.Vars(r)["id"]) + // convert string id to uint32 + id, err := u.Str2Uint32(mux.Vars(r)["id"]) + if err != nil { + u.FailureRespond(w, http.StatusBadRequest, "ID must be a number type.") + return + } task := &models.Task{ ID: id, UserId: decoded.UserId, } - err := task.GetTaskByUserId(db) + err = task.GetTaskByUserId(db) if err != nil { - u.FailureRespond(w, http.StatusNotFound, "Not found task") + u.SuccessRespond(w, http.StatusOK, "Not found task", nil) return } @@ -88,6 +86,7 @@ var Add = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { } //everything OK u.SuccessRespond(w, http.StatusCreated, "Success create task", map[string]interface{}{ + "id": fmt.Sprint(task.ID), "name": task.Name, "content": task.Content, "created_at": task.CreatedAt, @@ -96,26 +95,40 @@ var Add = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { var Edit = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { decoded := r.Context().Value("user").(*models.Token) - id := mux.Vars(r)["id"] // get id from url params + // convert string id to uint32 + id, err := u.Str2Uint32(mux.Vars(r)["id"]) + if err != nil { + u.FailureRespond(w, http.StatusBadRequest, "ID must be a number type.") + return + } + task := &models.Task{ + ID: id, UserId: decoded.UserId, } - err := json.NewDecoder(r.Body).Decode(task) + + // err := GetTaskByUserId(db) + err = json.NewDecoder(r.Body).Decode(task) if err != nil { u.FailureRespond(w, http.StatusBadRequest, err.Error()) return } var ( - name string = task.Name - content string = task.Content + newName string = task.Name + newContent string = task.Content ) - if name != "" { - task.Name = name + err = task.GetTaskByUserId(db) + if err != nil { + u.FailureRespond(w, http.StatusInternalServerError, "Somethings went wrong. Please try again"+err.Error()) + return + } + if newName != "" { + task.Name = newName } - if content != "" { - task.Content = content + if newContent != "" { + task.Content = newContent } - err = task.UpdateTaskById(db, id) + err = task.UpdateTaskById(db) if err != nil { u.FailureRespond(w, http.StatusBadRequest, err.Error()) return @@ -129,13 +142,19 @@ var Edit = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { var Delete = func(db *sql.DB, w http.ResponseWriter, r *http.Request) { decoded := r.Context().Value("user").(*models.Token) - id := mux.Vars(r)["id"] + // convert string id to uint32 + id, err := u.Str2Uint32(mux.Vars(r)["id"]) + if err != nil { + u.FailureRespond(w, http.StatusBadRequest, "ID must be a number type.") + return + } task := &models.Task{ + ID: id, UserId: decoded.UserId, } - err := task.DeleteTaskById(db, id) + err = task.DeleteTaskById(db) if err != nil { u.FailureRespond(w, http.StatusBadRequest, err.Error()) return diff --git a/controllers/task_test.go b/controllers/task_test.go index d34bd71b2..9cf165173 100644 --- a/controllers/task_test.go +++ b/controllers/task_test.go @@ -9,11 +9,12 @@ import ( // Pass ✅ func TestAdd(t *testing.T) { + signup() + token := getToken() payload := []byte(`{ "name" : "task name", "content" : "task content" }`) - req, _ := http.NewRequest("POST", "/api/tasks/add", bytes.NewBuffer(payload)) req.Header.Set("Authorization", token) res := executeRequest(req) @@ -36,56 +37,64 @@ func TestGetTasks(t *testing.T) { type tasksResponse struct { Status string Message string - Data []map[string]interface{} + Data []map[string]string } + token := getToken() // get user id here req, _ := http.NewRequest("GET", "/api/tasks", nil) req.Header.Set("Authorization", token) res := executeRequest(req) - var tr tasksResponse + tr := tasksResponse{} json.Unmarshal(res.Body.Bytes(), &tr) checkResponseCode(t, http.StatusOK, res.Code) - checkResponseStatus(t, "Success", r.Status) - checkResponseMessage(t, "Success", r.Message) + checkResponseStatus(t, "Success", tr.Status) + checkResponseMessage(t, "Success", tr.Message) // json.NewDecoder(res.Body).Decode(&m) - if r.Status != "Success" { - t.Errorf("Expected Status field to be 'Success' but. Got '%v'", r.Status) - } - if len(r.Data) != 8 { - t.Errorf("Expected tasks length to be '7'. Got '%v'", len(r.Data)) + for _, v := range tr.Data { + if v["name"] != "task name" { + t.Errorf("Expected task name is 'task name'. Got '%v'", v["name"]) + } + if v["content"] != "task content" { + t.Errorf("Expected task content is 'task content'. Got '%v'", v["content"]) + } } } // Pass ✅ func TestGetTask(t *testing.T) { - req, _ := http.NewRequest("GET", "/api/tasks/4", nil) + token := getToken() + id := getIdFromCreatedTask(token) + req, _ := http.NewRequest("GET", "/api/tasks/"+id, nil) // auth token req.Header.Set("Authorization", token) res := executeRequest(req) - json.Unmarshal(res.Body.Bytes(), &r) + re := response{} + json.Unmarshal(res.Body.Bytes(), &re) checkResponseCode(t, http.StatusOK, res.Code) - checkResponseStatus(t, "Success", r.Status) - checkResponseMessage(t, "Success", r.Message) + checkResponseStatus(t, "Success", re.Status) + checkResponseMessage(t, "Success", re.Message) - if r.Data["name"] != "task name" { - t.Errorf("Expected task name to be 'task name' value. Got '%v'", r.Data["name"]) + if re.Data["name"] != "task name" { + t.Errorf("Expected task name to be 'task name' value. Got '%v'", re.Data["name"]) } - if r.Data["content"] != "subtask1 \n subtask2" { - t.Errorf("Expected task content to be 'subtask1 \n subtask2' value. Got '%v'", r.Data["content"]) + if re.Data["content"] != "task content" { + t.Errorf("Expected task content to be 'task content' value'. Got '%v'", re.Data["content"]) } } // Pass ✅ func TestEdit(t *testing.T) { + token := getToken() + id := getIdFromCreatedTask(token) payload := []byte(`{ "name" : "task name", "content" : "task content" }`) - req, _ := http.NewRequest("PATCH", "/api/tasks/4", bytes.NewBuffer(payload)) + req, _ := http.NewRequest("PATCH", "/api/tasks/"+id, bytes.NewBuffer(payload)) req.Header.Set("Authorization", token) res := executeRequest(req) @@ -104,7 +113,9 @@ func TestEdit(t *testing.T) { // Pass ✅ func TestDelete(t *testing.T) { - req, _ := http.NewRequest("DELETE", "/api/tasks/3", nil) + token := getToken() + id := getIdFromCreatedTask(token) + req, _ := http.NewRequest("DELETE", "/api/tasks/"+id, nil) req.Header.Set("Authorization", token) res := executeRequest(req) @@ -117,5 +128,38 @@ func TestDelete(t *testing.T) { if r.Data != nil { t.Errorf("Expected type of Data to be 'nil' value. Got '%v'", r.Data) } - t.Logf("Response %v", r) + rollbackTask() +} + +func signup() { + payload := []byte(`{ + "name": "test_user", + "email": "test_user@gmail.com", + "password": "123456" + }`) + req, _ := http.NewRequest("POST", "/api/users/signup", bytes.NewBuffer(payload)) + req.Header.Set("Content-Type", "application/json") + res := executeRequest(req) + json.Unmarshal(res.Body.Bytes(), &r) + if r.Status == "Failure" { + return + } +} + +func getIdFromCreatedTask(token string) string { + type createTaskRes struct { + Status string + Message string + Data map[string]string + } + payload := []byte(`{ + "name" : "task name", + "content" : "task content" + }`) + req, _ := http.NewRequest("POST", "/api/tasks/add", bytes.NewBuffer(payload)) + req.Header.Set("Authorization", token) + res := executeRequest(req) + r := createTaskRes{} + json.Unmarshal(res.Body.Bytes(), &r) + return r.Data["id"] } diff --git a/controllers/user_test.go b/controllers/user_test.go index 32f4c69f7..6dfb4b44a 100644 --- a/controllers/user_test.go +++ b/controllers/user_test.go @@ -9,6 +9,7 @@ import ( // Pass ✅ func TestSignUp(t *testing.T) { + _ = rollbackUser() payload := []byte(`{ "name": "test_user", "email": "test_user@gmail.com", @@ -132,6 +133,7 @@ func TestDeleteMe(t *testing.T) { t.Errorf("Expected field of Data to be 'nil'. Got '%v'", r.Data) } rollbackUser() + rollbackTask() } func getToken() string { From e9a5b313062f936ffeeb0abb3c1d1ef6f7355bdd Mon Sep 17 00:00:00 2001 From: Quang Ha Date: Thu, 30 Jun 2022 19:24:54 +0700 Subject: [PATCH 18/41] update dockerfiles --- .dockerignore | 3 +++ Dockerfile | 30 ++++++++++++++++++++++++++++ app/app.go | 3 ++- controllers/controllers_main_test.go | 13 ++---------- controllers/payment_test.go | 12 ++++++----- controllers/task_test.go | 5 +++++ controllers/user_test.go | 4 ++++ docker-compose.yml | 25 +++++++++++++++++++++++ main.go | 3 ++- models/task.go | 10 +++++----- utils/utils.go | 9 +++++++++ utils/utils_test.go | 21 +++++++++++++++++++ 12 files changed, 115 insertions(+), 23 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 utils/utils_test.go diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..2c355df0c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +*_test.go +README.md +.vscode \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..b2ad05fd0 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,30 @@ +FROM golang:1.18-alpine + +# Set destination for COPY +WORKDIR /app + +# Download Go modules +COPY go.mod . +COPY go.sum . +RUN go mod download + +# Copy the source code. Note the slash at the end, as explained in +# https://docs.docker.com/engine/reference/builder/#copy +# COPY .env . +COPY . . +# Build +RUN go build -o /togo + +# This is for documentation purposes only. +# To actually open the port, runtime parameters +# must be supplied to the docker command. +EXPOSE 3000 + +# (Optional) environment variable that our dockerised +# application can make use of. The value of environment +# variables can also be set via parameters supplied +# to the docker command on the command line. +#ENV HTTP_PORT=8081 + +# Run +CMD [ "/togo" ] \ No newline at end of file diff --git a/app/app.go b/app/app.go index 63764e174..042cececc 100644 --- a/app/app.go +++ b/app/app.go @@ -18,11 +18,12 @@ type App struct { } func (a *App) Init() { - if err := godotenv.Load("C:\\Users\\quang\\Desktop\\Project\\Go\\internship\\togo\\.env"); err != nil { + if err := godotenv.Load(); err != nil { log.Fatal("Error loading .env file") } connectURL := os.Getenv("CONNECT_STR") db, err := sql.Open("postgres", connectURL) + if err != nil { log.Fatal(err) } diff --git a/controllers/controllers_main_test.go b/controllers/controllers_main_test.go index 946f2bc26..8f1eae369 100644 --- a/controllers/controllers_main_test.go +++ b/controllers/controllers_main_test.go @@ -17,9 +17,8 @@ type response struct { } var ( - a app.App - r response - token string = "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjUyLCJMaW1pdERheVRhc2tzIjoxMH0.PcgDnM8-LY0oyNiSfYRyQVIMryU1TfqWgEgq6geqEXc" + a app.App + r response ) func TestMain(m *testing.M) { @@ -68,11 +67,3 @@ func rollbackTask() error { } return nil } - -func rollbackPayment(email interface{}) error { - _, err := a.DB.Exec(`UPDATE users SET is_payment = $1, limit_day_tasks = $2 WHERE email = $3`, false, 10, email) - if err != nil { - return err - } - return nil -} diff --git a/controllers/payment_test.go b/controllers/payment_test.go index a0d300303..5bb79797b 100644 --- a/controllers/payment_test.go +++ b/controllers/payment_test.go @@ -8,6 +8,8 @@ import ( // Pass ✅ func TestPayment(t *testing.T) { + signup() + token := getToken() req, _ := http.NewRequest("POST", "/api/payments", nil) req.Header.Set("Authorization", token) res := executeRequest(req) @@ -17,11 +19,11 @@ func TestPayment(t *testing.T) { checkResponseStatus(t, "Success", r.Status) checkResponseMessage(t, "Success upgrade Premium account. Please login again to try new upgrade", r.Message) - if r.Data["name"] != "testinguser" { - t.Errorf("Expected user name is 'testinguser'. Got '%v'", r.Data["name"]) + if r.Data["name"] != "test_user" { + t.Errorf("Expected user name is 'test_user'. Got '%v'", r.Data["name"]) } - if r.Data["email"] != "testinguser@gmail.com" { - t.Errorf("Expected user email is 'testinguser@gmail.com'. Got '%v'", r.Data["email"]) + if r.Data["email"] != "test_user@gmail.com" { + t.Errorf("Expected user email is 'test_user@gmail.com'. Got '%v'", r.Data["email"]) } if r.Data["is_payment"] != true { t.Errorf("Expected user is_payment field is 'true'. Got '%v'", r.Data["is_payment"]) @@ -30,5 +32,5 @@ func TestPayment(t *testing.T) { t.Errorf("Expected user limit task field is '20'. Got '%v'", r.Data["limit_day_tasks"]) } // rollback before Payments - rollbackPayment(r.Data["email"]) + rollbackUser() } diff --git a/controllers/task_test.go b/controllers/task_test.go index 9cf165173..ec20df535 100644 --- a/controllers/task_test.go +++ b/controllers/task_test.go @@ -39,6 +39,7 @@ func TestGetTasks(t *testing.T) { Message string Data []map[string]string } + signup() token := getToken() // get user id here req, _ := http.NewRequest("GET", "/api/tasks", nil) @@ -64,6 +65,7 @@ func TestGetTasks(t *testing.T) { // Pass ✅ func TestGetTask(t *testing.T) { + signup() token := getToken() id := getIdFromCreatedTask(token) req, _ := http.NewRequest("GET", "/api/tasks/"+id, nil) @@ -88,6 +90,7 @@ func TestGetTask(t *testing.T) { // Pass ✅ func TestEdit(t *testing.T) { + signup() token := getToken() id := getIdFromCreatedTask(token) payload := []byte(`{ @@ -113,6 +116,7 @@ func TestEdit(t *testing.T) { // Pass ✅ func TestDelete(t *testing.T) { + signup() token := getToken() id := getIdFromCreatedTask(token) req, _ := http.NewRequest("DELETE", "/api/tasks/"+id, nil) @@ -129,6 +133,7 @@ func TestDelete(t *testing.T) { t.Errorf("Expected type of Data to be 'nil' value. Got '%v'", r.Data) } rollbackTask() + rollbackUser() } func signup() { diff --git a/controllers/user_test.go b/controllers/user_test.go index 6dfb4b44a..7042d7e33 100644 --- a/controllers/user_test.go +++ b/controllers/user_test.go @@ -34,6 +34,7 @@ func TestSignUp(t *testing.T) { // Pass ✅ func TestLogin(t *testing.T) { + signup() payload := []byte(`{ "email": "test_user@gmail.com", "password": "123456" @@ -60,6 +61,7 @@ func TestLogin(t *testing.T) { // Pass ✅ func TestGetMe(t *testing.T) { // get token from test user + signup() token := getToken() req, _ := http.NewRequest("GET", "/api/users/me", nil) req.Header.Set("Authorization", token) @@ -89,6 +91,7 @@ func TestGetMe(t *testing.T) { // Pass ✅ func TestUpdateMe(t *testing.T) { // get token from test user + signup() token := getToken() payload := []byte(`{ "name": "updated_test_user", @@ -116,6 +119,7 @@ func TestUpdateMe(t *testing.T) { // Pass ✅ func TestDeleteMe(t *testing.T) { + signup() token := getToken() payload := []byte(`{ "password": "123456" diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..d0e630128 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,25 @@ +version: "3.7" +services: + database: + image: postgres + restart: always + env_file: + - .env + ports: + - "5000:5432" + volumes: + - data:/var/lib/postgresql/data + server: + build: + context: . + dockerfile: Dockerfile + env_file: + - .env + depends_on: + - database + networks: + - default + ports: + - "3000:3000" +volumes: + data: \ No newline at end of file diff --git a/main.go b/main.go index 394ec4c9f..0e35fb520 100644 --- a/main.go +++ b/main.go @@ -5,6 +5,7 @@ import ( "syscall" "github.com/joho/godotenv" + _ "github.com/lib/pq" "github.com/manabie-com/togo/app" ) @@ -17,7 +18,7 @@ func main() { port = "8000" } app := &app.App{} - defer app.DB.Close() app.Init() app.Run(":" + port) + defer app.DB.Close() } diff --git a/models/task.go b/models/task.go index 54df1b9ef..d1df9c04b 100644 --- a/models/task.go +++ b/models/task.go @@ -14,7 +14,7 @@ type Task struct { } func (t *Task) InsertTask(db *sql.DB) error { - err := db.QueryRow(`INSERT INTO tasks(name, content, user_id) VALUES($3, $2, $1) RETURNING name, content, created_at`, t.UserId, t.Content, t.Name).Scan(&t.Name, &t.Content, &t.CreatedAt) + err := db.QueryRow(`INSERT INTO tasks(name, content, user_id) VALUES($3, $2, $1) RETURNING id, name, content, created_at`, t.UserId, t.Content, t.Name).Scan(&t.ID, &t.Name, &t.Content, &t.CreatedAt) if err != nil { return err } @@ -53,16 +53,16 @@ func (t *Task) IsLimit(db *sql.DB, todayTasksLimit uint) bool { return tasksLength == todayTasksLimit } -func (t *Task) DeleteTaskById(db *sql.DB, id string) error { - _, err := db.Exec(`DELETE FROM tasks WHERE id = $1 AND user_id = $2`, id, t.UserId) +func (t *Task) DeleteTaskById(db *sql.DB) error { + _, err := db.Exec(`DELETE FROM tasks WHERE id = $1 AND user_id = $2`, t.ID, t.UserId) if err != nil { return err } return nil } -func (t *Task) UpdateTaskById(db *sql.DB, id string) error { - _, err := db.Exec(`UPDATE tasks SET name = $3 WHERE id = $1 AND user_id = $2 RETURNING name, content, created_at`, id, t.UserId, t.Name) +func (t *Task) UpdateTaskById(db *sql.DB) error { + _, err := db.Exec(`UPDATE tasks SET name = $3 WHERE id = $1 AND user_id = $2 RETURNING name, content, created_at`, t.ID, t.UserId, t.Name) if err != nil { return err } diff --git a/utils/utils.go b/utils/utils.go index 6b0f4846b..42e079957 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -3,6 +3,7 @@ package utils import ( "encoding/json" "net/http" + "strconv" ) type response struct { @@ -29,3 +30,11 @@ func FailureRespond(w http.ResponseWriter, statusCode int, message string) { w.WriteHeader(statusCode) json.NewEncoder(w).Encode(res) } + +func Str2Uint32(str string) (uint32, error) { + u64, err := strconv.ParseUint(str, 10, 32) + if err != nil { + return 0, err + } + return uint32(u64), nil +} diff --git a/utils/utils_test.go b/utils/utils_test.go new file mode 100644 index 000000000..040489bae --- /dev/null +++ b/utils/utils_test.go @@ -0,0 +1,21 @@ +package utils + +import ( + "reflect" + "testing" +) + +func TestStr2Uint32(t *testing.T) { + var predict uint32 = 100 + actual, err := Str2Uint32("100") + + if err != nil { + t.Errorf("expected %v, but got %v", nil, err) + } + if predict != actual { + t.Errorf("expected %v, but got %v", predict, actual) + } + if reflect.TypeOf(actual) != reflect.TypeOf(predict) { + t.Errorf("expected %v, but got %v", "uint32", reflect.TypeOf(actual)) + } +} From af6aa5f5222461fef7254635b82d803a80cb6c62 Mon Sep 17 00:00:00 2001 From: Quang Ha Date: Thu, 30 Jun 2022 23:17:42 +0700 Subject: [PATCH 19/41] write README.md --- README.md | 46 ++++++++++++++++++++++++++++++++++++++++++++++ models/user.go | 4 ++-- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e69de29bb..b7d578871 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,46 @@ +# TOGO + +## Description + +**Simple API using Golang, Postgresql and jwt for authentication** + +## How to run your code locally? +- Git clone repository + + ```bash + git clone https://github.com/qanghaa/togo.git + ``` +- Dependencies Installation + + ```bash + go install + ``` +- Configuration Server + + create `.env` file + + add your secret enviroment variables below + ``` + PORT=3000 + CONNECT_STR=postgres://:@localhost/?sslmode=disable + SECRET_TOKEN= + ``` +- go run main.go + + ```bash + OR + ``` +- go run github.com/manabie-com/togo + +## Sample “curl” Command:** [here](https://documenter.getpostman.com/view/15522883/UzBvHPBC) + +## How to run your unit tests locally? + - Go to ***togo*** directory + - type in cmd: ```go run test ./...``` + +## What do you love about your solution? + Overall, nothing outstanding. However I quite like the payment feature. Although the implementation is simple, it will help the user become a Premium user LOL. + This feature helps users to overcome the limit of creating tasks in 1 day (20 tasks/day) instead òf 10 as usual. + +## What else do you want us to know about however you do not have enough time to complete? + Probably Docker. I spent 2 days learning and trying to work on it. However I failed and can't make it easier for you to use my source code. diff --git a/models/user.go b/models/user.go index 9c706562a..23d9e9527 100644 --- a/models/user.go +++ b/models/user.go @@ -6,8 +6,8 @@ import ( type User struct { ID uint32 `json:"id" validate:"omitempty"` - Email string `json:"email" validate:"required,email,min=10,max=30"` - Name string `json:"name" validate:"required,min=5,max=20"` + Email string `json:"email" validate:"required,email,min=10,max=40"` + Name string `json:"name" validate:"required,min=5,max=30"` Password string `json:"password" validate:"required,min=6,max=20"` IsPayment bool `json:"isPayment" validate:"omitempty"` IsActive bool `json:"isActive"` From 4ffdb4adef4634124287b2a5964e87c3e6b25edc Mon Sep 17 00:00:00 2001 From: Quang Ha Date: Thu, 30 Jun 2022 23:20:00 +0700 Subject: [PATCH 20/41] fix preview :D --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b7d578871..12fea0d8b 100644 --- a/README.md +++ b/README.md @@ -25,12 +25,14 @@ CONNECT_STR=postgres://:@localhost/?sslmode=disable SECRET_TOKEN= ``` -- go run main.go + ```bash + go run main.go + ``` + OR ```bash - OR + go run github.com/manabie-com/togo ``` -- go run github.com/manabie-com/togo ## Sample “curl” Command:** [here](https://documenter.getpostman.com/view/15522883/UzBvHPBC) From 35fff072be508a52342a374ca7f9be79d13d3c9c Mon Sep 17 00:00:00 2001 From: Quang Ha Date: Thu, 30 Jun 2022 23:22:46 +0700 Subject: [PATCH 21/41] update README --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 12fea0d8b..27219c1b3 100644 --- a/README.md +++ b/README.md @@ -17,23 +17,23 @@ ``` - Configuration Server - create `.env` file + create `.env` file - add your secret enviroment variables below + and add your secret enviroment variables below ``` PORT=3000 CONNECT_STR=postgres://:@localhost/?sslmode=disable SECRET_TOKEN= ``` - +- Run Server ```bash go run main.go ``` - OR + OR ```bash go run github.com/manabie-com/togo ``` - +- Go to Sample 'cur' in below document ## Sample “curl” Command:** [here](https://documenter.getpostman.com/view/15522883/UzBvHPBC) ## How to run your unit tests locally? From 641b34b25ee9798f390b88aeb4a2a5c1c1765eb7 Mon Sep 17 00:00:00 2001 From: Nguyen Quang Ha <72819957+qanghaa@users.noreply.github.com> Date: Thu, 30 Jun 2022 23:31:55 +0700 Subject: [PATCH 22/41] Update README.md --- README.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 27219c1b3..af82d1277 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ## Description -**Simple API using Golang, Postgresql and jwt for authentication** +  **Simple API using Golang, Postgresql and jwt for authentication** ## How to run your code locally? - Git clone repository @@ -33,16 +33,15 @@ ```bash go run github.com/manabie-com/togo ``` -- Go to Sample 'cur' in below document -## Sample “curl” Command:** [here](https://documenter.getpostman.com/view/15522883/UzBvHPBC) +- Go to Sample 'curl' in below document +## Sample “curl” Command: [here](https://documenter.getpostman.com/view/15522883/UzBvHPBC) ## How to run your unit tests locally? - Go to ***togo*** directory - type in cmd: ```go run test ./...``` ## What do you love about your solution? - Overall, nothing outstanding. However I quite like the payment feature. Although the implementation is simple, it will help the user become a Premium user LOL. - This feature helps users to overcome the limit of creating tasks in 1 day (20 tasks/day) instead òf 10 as usual. +   Overall, nothing outstanding. However I quite like the payment feature. Although the implementation is simple, it will help the user become   a Premium user LOL. This feature helps users to overcome the limit of creating tasks in 1 day (20 tasks/day) instead òf 10 as usual. ## What else do you want us to know about however you do not have enough time to complete? - Probably Docker. I spent 2 days learning and trying to work on it. However I failed and can't make it easier for you to use my source code. +   Probably Docker. I spent 2 days learning and trying to work on it. However I failed and can't make it easier for you to use my source code. From 15f3e31751116e084ca227cd970ef71c3536a1b6 Mon Sep 17 00:00:00 2001 From: Nguyen Quang Ha <72819957+qanghaa@users.noreply.github.com> Date: Fri, 1 Jul 2022 09:32:48 +0700 Subject: [PATCH 23/41] Update README.md --- README.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index af82d1277..6972eccee 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@   **Simple API using Golang, Postgresql and jwt for authentication** -## How to run your code locally? +## 1. How to run your code locally? - Git clone repository ```bash @@ -13,7 +13,7 @@ - Dependencies Installation ```bash - go install + go mod download ``` - Configuration Server @@ -33,15 +33,16 @@ ```bash go run github.com/manabie-com/togo ``` -- Go to Sample 'curl' in below document -## Sample “curl” Command: [here](https://documenter.getpostman.com/view/15522883/UzBvHPBC) +- Go to Sample 'curl' in below document :point_down: +## 2. Sample “curl” Command: +   API Document: [here](https://documenter.getpostman.com/view/15522883/UzBvHPBC) -## How to run your unit tests locally? +## 3. How to run your unit tests locally? - Go to ***togo*** directory - type in cmd: ```go run test ./...``` -## What do you love about your solution? +## 4. What do you love about your solution?   Overall, nothing outstanding. However I quite like the payment feature. Although the implementation is simple, it will help the user become   a Premium user LOL. This feature helps users to overcome the limit of creating tasks in 1 day (20 tasks/day) instead òf 10 as usual. -## What else do you want us to know about however you do not have enough time to complete? +## 5. What else do you want us to know about however you do not have enough time to complete?   Probably Docker. I spent 2 days learning and trying to work on it. However I failed and can't make it easier for you to use my source code. From 1103a36d95c82d26eaeeb2be1c7563835ee9d7c1 Mon Sep 17 00:00:00 2001 From: Nguyen Quang Ha <72819957+qanghaa@users.noreply.github.com> Date: Fri, 1 Jul 2022 11:21:07 +0700 Subject: [PATCH 24/41] changes Dockerfile to build postgresql container --- Dockerfile | 35 +++++------------------------------ 1 file changed, 5 insertions(+), 30 deletions(-) diff --git a/Dockerfile b/Dockerfile index b2ad05fd0..1e28636f4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,30 +1,5 @@ -FROM golang:1.18-alpine - -# Set destination for COPY -WORKDIR /app - -# Download Go modules -COPY go.mod . -COPY go.sum . -RUN go mod download - -# Copy the source code. Note the slash at the end, as explained in -# https://docs.docker.com/engine/reference/builder/#copy -# COPY .env . -COPY . . -# Build -RUN go build -o /togo - -# This is for documentation purposes only. -# To actually open the port, runtime parameters -# must be supplied to the docker command. -EXPOSE 3000 - -# (Optional) environment variable that our dockerised -# application can make use of. The value of environment -# variables can also be set via parameters supplied -# to the docker command on the command line. -#ENV HTTP_PORT=8081 - -# Run -CMD [ "/togo" ] \ No newline at end of file +FROM postgres +ENV POSTGRES_PASSWORD manabie +ENV POSTGRES_DB togo +COPY script.sql /docker-entrypoint-initdb.d/ +VOLUME ./postgres-data:/var/lib/postgresql/data From 6ffcce9899571d7103149d00aa5863a474a3b492 Mon Sep 17 00:00:00 2001 From: Nguyen Quang Ha <72819957+qanghaa@users.noreply.github.com> Date: Fri, 1 Jul 2022 11:22:53 +0700 Subject: [PATCH 25/41] add argument for Init, to help load env file --- app/app.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/app/app.go b/app/app.go index 042cececc..1913766c0 100644 --- a/app/app.go +++ b/app/app.go @@ -5,10 +5,8 @@ import ( "fmt" "log" "net/http" - "os" "github.com/gorilla/mux" - "github.com/joho/godotenv" _ "github.com/lib/pq" ) @@ -17,11 +15,7 @@ type App struct { DB *sql.DB } -func (a *App) Init() { - if err := godotenv.Load(); err != nil { - log.Fatal("Error loading .env file") - } - connectURL := os.Getenv("CONNECT_STR") +func (a *App) Init(connectURL string) { db, err := sql.Open("postgres", connectURL) if err != nil { From 720b0ef3d2ae48b73f7cf522ebbd30bc1de854c1 Mon Sep 17 00:00:00 2001 From: Nguyen Quang Ha <72819957+qanghaa@users.noreply.github.com> Date: Fri, 1 Jul 2022 11:23:43 +0700 Subject: [PATCH 26/41] update load connect_str env variable --- main.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index 0e35fb520..638a2a95f 100644 --- a/main.go +++ b/main.go @@ -17,8 +17,12 @@ func main() { if !ok { port = "8000" } + CONNECT_STR, ok := syscall.Getenv("CONNECT_STR") + if !ok { + log.Fatal("Please set CONNECT_STR environment") + } app := &app.App{} - app.Init() + app.Init(CONNECT_STR) app.Run(":" + port) defer app.DB.Close() } From 77c163c329d7d5b2f5ce497af253bc983fca748d Mon Sep 17 00:00:00 2001 From: Nguyen Quang Ha <72819957+qanghaa@users.noreply.github.com> Date: Fri, 1 Jul 2022 11:24:20 +0700 Subject: [PATCH 27/41] Update controllers_main_test.go --- controllers/controllers_main_test.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/controllers/controllers_main_test.go b/controllers/controllers_main_test.go index 8f1eae369..5add47493 100644 --- a/controllers/controllers_main_test.go +++ b/controllers/controllers_main_test.go @@ -1,12 +1,15 @@ package controllers_test import ( + "log" "os" + "syscall" "testing" "net/http" "net/http/httptest" + "github.com/joho/godotenv" "github.com/manabie-com/togo/app" ) @@ -22,8 +25,15 @@ var ( ) func TestMain(m *testing.M) { + if err := godotenv.Load("../.env"); err != nil { + log.Fatal("Error loading .env file") + } + CONNECT_STR, ok := syscall.Getenv("CONNECT_STR") + if !ok { + log.Fatal("Please set CONNECT_STR environment") + } a = app.App{} - a.Init() + a.Init(CONNECT_STR) code := m.Run() os.Exit(code) } From 28f0702613251d56568c6c10bc50f89d0c2b531b Mon Sep 17 00:00:00 2001 From: Nguyen Quang Ha <72819957+qanghaa@users.noreply.github.com> Date: Fri, 1 Jul 2022 11:26:02 +0700 Subject: [PATCH 28/41] add script.sql to auto create table in docker container --- script.sql | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 script.sql diff --git a/script.sql b/script.sql new file mode 100644 index 000000000..2cd310850 --- /dev/null +++ b/script.sql @@ -0,0 +1,29 @@ +CREATE TABLE users +( + id serial NOT NULL, + email VARCHAR(40) NOT NULL, + name VARCHAR(30) NOT NULL, + password VARCHAR(20) NOT NULL, + is_payment boolean DEFAULT false, + limit_day_tasks smallint DEFAULT 10, + is_active boolean DEFAULT true, + CONSTRAINT users_pkey PRIMARY KEY (id), + CONSTRAINT email UNIQUE (email) +); + + +CREATE TABLE tasks +( + id serial NOT NULL, + name VARCHAR(100) NOT NULL, + content text, + created_at timestamp without time zone NOT NULL DEFAULT now(), + user_id integer NOT NULL, + PRIMARY KEY (id), + FOREIGN KEY (user_id) + REFERENCES users (id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE NO ACTION + NOT VALID +); + From 1b6c3e739488048e2dec4bdd2203c6adad509c71 Mon Sep 17 00:00:00 2001 From: Nguyen Quang Ha <72819957+qanghaa@users.noreply.github.com> Date: Fri, 1 Jul 2022 11:47:54 +0700 Subject: [PATCH 29/41] Update README.md --- README.md | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6972eccee..026da2d6f 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,15 @@ ```bash git clone https://github.com/qanghaa/togo.git ``` +- Go to ***togo*** directory +- Using Docker to run Postgresql + ```bash + docker build -t togo . + ``` + + ```bash + docker run -dp 4001:5432 togo + ``` - Dependencies Installation ```bash @@ -17,12 +26,13 @@ ``` - Configuration Server - create `.env` file + - create `.env` file - and add your secret enviroment variables below + - and add your secret enviroment variables below + ``` PORT=3000 - CONNECT_STR=postgres://:@localhost/?sslmode=disable + CONNECT_STR=postgres://postgres:manabie@localhost:4001/togo?sslmode=disable SECRET_TOKEN= ``` - Run Server @@ -39,7 +49,7 @@ ## 3. How to run your unit tests locally? - Go to ***togo*** directory - - type in cmd: ```go run test ./...``` + - type in cmd: ```go test ./...``` ## 4. What do you love about your solution?   Overall, nothing outstanding. However I quite like the payment feature. Although the implementation is simple, it will help the user become   a Premium user LOL. This feature helps users to overcome the limit of creating tasks in 1 day (20 tasks/day) instead òf 10 as usual. From b694c6a77f7bb9f8e372b11cb251e8d79d208ae7 Mon Sep 17 00:00:00 2001 From: Nguyen Quang Ha <72819957+qanghaa@users.noreply.github.com> Date: Fri, 1 Jul 2022 12:56:06 +0700 Subject: [PATCH 30/41] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 026da2d6f..f433db273 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ - type in cmd: ```go test ./...``` ## 4. What do you love about your solution? -   Overall, nothing outstanding. However I quite like the payment feature. Although the implementation is simple, it will help the user become   a Premium user LOL. This feature helps users to overcome the limit of creating tasks in 1 day (20 tasks/day) instead òf 10 as usual. +   Overall, nothing outstanding. However I quite like the payment feature. Although the implementation is simple, it will help the user become   a Premium user LOL. This feature helps users to overcome the limit of creating tasks in 1 day (20 tasks/day) instead òf 10 as usual. Not related to feature, probably Docker, I spent 2 days learning and trying to work on it and I was able to use it in this project :D. ## 5. What else do you want us to know about however you do not have enough time to complete? -   Probably Docker. I spent 2 days learning and trying to work on it. However I failed and can't make it easier for you to use my source code. +   Probably Testing. I haven't writed unit test enough possible scenarios with my API yet. From f950754f898ac168021dbbc91d0aa765481d924a Mon Sep 17 00:00:00 2001 From: Nguyen Quang Ha <72819957+qanghaa@users.noreply.github.com> Date: Fri, 1 Jul 2022 12:58:44 +0700 Subject: [PATCH 31/41] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f433db273..f205000af 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ - type in cmd: ```go test ./...``` ## 4. What do you love about your solution? -   Overall, nothing outstanding. However I quite like the payment feature. Although the implementation is simple, it will help the user become   a Premium user LOL. This feature helps users to overcome the limit of creating tasks in 1 day (20 tasks/day) instead òf 10 as usual. Not related to feature, probably Docker, I spent 2 days learning and trying to work on it and I was able to use it in this project :D. +   Overall, nothing outstanding. However I quite like the payment feature. Although the implementation is simple, it will help the user become a Premium user LOL. This feature helps users to overcome the limit of creating tasks in 1 day (20 tasks/day) instead òf 10 as usual. Not related to feature, probably Docker, I spent 2 days learning and trying to work on it and I was able to use it in this project :D. ## 5. What else do you want us to know about however you do not have enough time to complete?   Probably Testing. I haven't writed unit test enough possible scenarios with my API yet. From d3ab0da529a1dbe80543d87bd1ede21e11e4cedf Mon Sep 17 00:00:00 2001 From: Nguyen Quang Ha <72819957+qanghaa@users.noreply.github.com> Date: Fri, 1 Jul 2022 15:10:32 +0700 Subject: [PATCH 32/41] Update README.md --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f205000af..0f249fe55 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,10 @@ ## 3. How to run your unit tests locally? - Go to ***togo*** directory - - type in cmd: ```go test ./...``` + - type in cmd: + ```bash + go test ./... + ``` ## 4. What do you love about your solution?   Overall, nothing outstanding. However I quite like the payment feature. Although the implementation is simple, it will help the user become a Premium user LOL. This feature helps users to overcome the limit of creating tasks in 1 day (20 tasks/day) instead òf 10 as usual. Not related to feature, probably Docker, I spent 2 days learning and trying to work on it and I was able to use it in this project :D. From 6f3bc86e1300021b8bb506c4d6344411fc4657ab Mon Sep 17 00:00:00 2001 From: Quang Ha Date: Sun, 3 Jul 2022 11:29:40 +0700 Subject: [PATCH 33/41] update --- .dockerignore | 4 ++-- controllers/controllers_main_test.go | 6 +++--- Dockerfile => db.Dockerfile | 2 +- docker-compose.yml | 15 +++++++-------- server.Dockerfile | 7 +++++++ 5 files changed, 20 insertions(+), 14 deletions(-) rename Dockerfile => db.Dockerfile (69%) create mode 100644 server.Dockerfile diff --git a/.dockerignore b/.dockerignore index 2c355df0c..0c241635b 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,3 @@ -*_test.go README.md -.vscode \ No newline at end of file +.vscode +db \ No newline at end of file diff --git a/controllers/controllers_main_test.go b/controllers/controllers_main_test.go index 5add47493..8ad66b0d4 100644 --- a/controllers/controllers_main_test.go +++ b/controllers/controllers_main_test.go @@ -28,12 +28,12 @@ func TestMain(m *testing.M) { if err := godotenv.Load("../.env"); err != nil { log.Fatal("Error loading .env file") } - CONNECT_STR, ok := syscall.Getenv("CONNECT_STR") + CONNECT_STR_FOR_TEST, ok := syscall.Getenv("CONNECT_STR_FOR_TEST") if !ok { - log.Fatal("Please set CONNECT_STR environment") + log.Fatal("Please set CONNECT_STR_FOR_TEST environment") } a = app.App{} - a.Init(CONNECT_STR) + a.Init(CONNECT_STR_FOR_TEST) code := m.Run() os.Exit(code) } diff --git a/Dockerfile b/db.Dockerfile similarity index 69% rename from Dockerfile rename to db.Dockerfile index 1e28636f4..6c7a6986a 100644 --- a/Dockerfile +++ b/db.Dockerfile @@ -2,4 +2,4 @@ FROM postgres ENV POSTGRES_PASSWORD manabie ENV POSTGRES_DB togo COPY script.sql /docker-entrypoint-initdb.d/ -VOLUME ./postgres-data:/var/lib/postgresql/data +VOLUME ./postgres-data:/var/lib/postgresql/data \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index d0e630128..caaf1b5a3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,25 +1,24 @@ version: "3.7" services: database: - image: postgres + build: + context: . + dockerfile: db.Dockerfile restart: always env_file: - .env ports: - - "5000:5432" - volumes: - - data:/var/lib/postgresql/data + - "2345:5432" server: build: context: . - dockerfile: Dockerfile + dockerfile: server.Dockerfile env_file: - .env + restart: always depends_on: - database networks: - default ports: - - "3000:3000" -volumes: - data: \ No newline at end of file + - "3000:3000" diff --git a/server.Dockerfile b/server.Dockerfile new file mode 100644 index 000000000..adda91203 --- /dev/null +++ b/server.Dockerfile @@ -0,0 +1,7 @@ +FROM golang:1.18.1-alpine +WORKDIR /app +COPY . . +RUN go mod download +RUN go build -o /togo +EXPOSE 8000 +CMD [ "/togo" ] \ No newline at end of file From 8959832848579c437ece59302dfede4ba83fa2b9 Mon Sep 17 00:00:00 2001 From: Nguyen Quang Ha <72819957+qanghaa@users.noreply.github.com> Date: Sun, 3 Jul 2022 11:56:33 +0700 Subject: [PATCH 34/41] Update README.md --- README.md | 35 ++++++++--------------------------- 1 file changed, 8 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 0f249fe55..eb6941732 100644 --- a/README.md +++ b/README.md @@ -5,47 +5,28 @@   **Simple API using Golang, Postgresql and jwt for authentication** ## 1. How to run your code locally? +### Requirements +- Install [Docker Engine](https://docs.docker.com/engine/install/ubuntu/) +- Install [docker-compose](https://docs.docker.com/compose/install/) - Git clone repository ```bash git clone https://github.com/qanghaa/togo.git ``` - Go to ***togo*** directory -- Using Docker to run Postgresql +- Using `docker-compose` commands ```bash - docker build -t togo . + # this command make sure next command working as expected + docker-compose down --volumes . ``` ```bash - docker run -dp 4001:5432 togo + docker-compose up ``` -- Dependencies Installation - - ```bash - go mod download - ``` -- Configuration Server - - - create `.env` file - - and add your secret enviroment variables below - - ``` - PORT=3000 - CONNECT_STR=postgres://postgres:manabie@localhost:4001/togo?sslmode=disable - SECRET_TOKEN= - ``` -- Run Server - ```bash - go run main.go - ``` - OR - ```bash - go run github.com/manabie-com/togo - ``` -- Go to Sample 'curl' in below document :point_down: ## 2. Sample “curl” Command:   API Document: [here](https://documenter.getpostman.com/view/15522883/UzBvHPBC) +
  Note: ***Using Bearer Authorization Header for endpoints required*** ## 3. How to run your unit tests locally? - Go to ***togo*** directory From a3cdc84ae1c8cfbc67e1dc3ab4ac6364305b1759 Mon Sep 17 00:00:00 2001 From: Quang Ha Date: Sun, 3 Jul 2022 11:59:37 +0700 Subject: [PATCH 35/41] push .env --- .env | 6 ++++++ .gitignore | 3 +-- 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 .env diff --git a/.env b/.env new file mode 100644 index 000000000..0dc5a0d1b --- /dev/null +++ b/.env @@ -0,0 +1,6 @@ +PORT=3000 +CONNECT_STR=postgres://postgres:manabie@database:5432/togo?sslmode=disable +SECRET_TOKEN=token +POSTGRES_PASSWORD=manabie +POSTGRES_DB=togo +CONNECT_STR_FOR_TEST=postgres://postgres:manabie@localhost:2345/togo?sslmode=disable \ No newline at end of file diff --git a/.gitignore b/.gitignore index c584ca2a3..a512c8b38 100644 --- a/.gitignore +++ b/.gitignore @@ -16,5 +16,4 @@ out/ -.idea -.env \ No newline at end of file +.idea \ No newline at end of file From d3236ca6f21ee8b3506e39248ee76187ab81265d Mon Sep 17 00:00:00 2001 From: Nguyen Quang Ha <72819957+qanghaa@users.noreply.github.com> Date: Sun, 3 Jul 2022 12:03:18 +0700 Subject: [PATCH 36/41] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index eb6941732..c14bc27fa 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ ```bash git clone https://github.com/qanghaa/togo.git ``` -- Go to ***togo*** directory +- Go to ***togo***:open_file_folder: directory - Using `docker-compose` commands ```bash # this command make sure next command working as expected From c559514d0664844b331e9ddb82e6194cbf30c572 Mon Sep 17 00:00:00 2001 From: Nguyen Quang Ha <72819957+qanghaa@users.noreply.github.com> Date: Sun, 3 Jul 2022 12:03:48 +0700 Subject: [PATCH 37/41] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c14bc27fa..349b89a4b 100644 --- a/README.md +++ b/README.md @@ -39,4 +39,4 @@   Overall, nothing outstanding. However I quite like the payment feature. Although the implementation is simple, it will help the user become a Premium user LOL. This feature helps users to overcome the limit of creating tasks in 1 day (20 tasks/day) instead òf 10 as usual. Not related to feature, probably Docker, I spent 2 days learning and trying to work on it and I was able to use it in this project :D. ## 5. What else do you want us to know about however you do not have enough time to complete? -   Probably Testing. I haven't writed unit test enough possible scenarios with my API yet. +Probably Testing. I haven't writed unit test enough possible scenarios with my API yet. From 8dcb769b9d1063941e7530e3dee03adb367d31f8 Mon Sep 17 00:00:00 2001 From: Nguyen Quang Ha <72819957+qanghaa@users.noreply.github.com> Date: Sun, 3 Jul 2022 12:05:40 +0700 Subject: [PATCH 38/41] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 349b89a4b..1da9c7df7 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,9 @@   **Simple API using Golang, Postgresql and jwt for authentication** ## 1. How to run your code locally? -### Requirements -- Install [Docker Engine](https://docs.docker.com/engine/install/ubuntu/) -- Install [docker-compose](https://docs.docker.com/compose/install/) +- Requirements + - Install [Docker Engine](https://docs.docker.com/engine/install/) + - Install [docker-compose](https://docs.docker.com/compose/install/) - Git clone repository ```bash From a9a4ca468a7242de8676bd506dcf29b815d4c5ad Mon Sep 17 00:00:00 2001 From: Nguyen Quang Ha <72819957+qanghaa@users.noreply.github.com> Date: Sun, 3 Jul 2022 13:25:02 +0700 Subject: [PATCH 39/41] Update server.Dockerfile --- server.Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server.Dockerfile b/server.Dockerfile index adda91203..4f8e9796c 100644 --- a/server.Dockerfile +++ b/server.Dockerfile @@ -1,7 +1,7 @@ -FROM golang:1.18.1-alpine +FROM golang:1.18-alpine WORKDIR /app COPY . . RUN go mod download RUN go build -o /togo -EXPOSE 8000 -CMD [ "/togo" ] \ No newline at end of file +EXPOSE 3000 +CMD [ "/togo" ] From c2b25170261561465da3ab94714a629dec84419a Mon Sep 17 00:00:00 2001 From: Nguyen Quang Ha <72819957+qanghaa@users.noreply.github.com> Date: Sun, 3 Jul 2022 13:32:45 +0700 Subject: [PATCH 40/41] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1da9c7df7..87910797f 100644 --- a/README.md +++ b/README.md @@ -17,11 +17,11 @@ - Using `docker-compose` commands ```bash # this command make sure next command working as expected - docker-compose down --volumes . + sudo docker-compose down --volumes . ``` ```bash - docker-compose up + sudo docker-compose up ``` ## 2. Sample “curl” Command: From 54591ec2137fb73b55f271ff68b3183a4154df0d Mon Sep 17 00:00:00 2001 From: Nguyen Quang Ha <72819957+qanghaa@users.noreply.github.com> Date: Mon, 4 Jul 2022 09:06:08 +0700 Subject: [PATCH 41/41] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 87910797f..41fe3b2b5 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ git clone https://github.com/qanghaa/togo.git ``` - Go to ***togo***:open_file_folder: directory -- Using `docker-compose` commands +- Using `docker-compose` commands with ***root*** permission ```bash # this command make sure next command working as expected sudo docker-compose down --volumes . @@ -29,7 +29,7 @@
  Note: ***Using Bearer Authorization Header for endpoints required*** ## 3. How to run your unit tests locally? - - Go to ***togo*** directory + - Go to ***togo***:open_file_folder: directory - type in cmd: ```bash go test ./...