From 0e15abbb01557e6bac0e17bbc6a311f0c2b2fd32 Mon Sep 17 00:00:00 2001 From: oleg Date: Sat, 6 Jan 2024 12:45:39 +0300 Subject: [PATCH 01/18] add handlers --- .gitignore | 1 + Makefile | 22 +++ cmd/gophermart/main.go | 73 ++++++++- go.mod | 10 ++ go.sum | 48 ++++++ internal/handler/handler.go | 303 ++++++++++++++++++++++++++++++++++++ 6 files changed, 456 insertions(+), 1 deletion(-) create mode 100644 Makefile create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/handler/handler.go diff --git a/.gitignore b/.gitignore index 50d43ce..39e9857 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ # Binaries cmd/gophermart/main cmd/gophermart/gophermart +bin/ # Dependency directories (remove the comment below to include it) vendor/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d22a75a --- /dev/null +++ b/Makefile @@ -0,0 +1,22 @@ +APP=./bin/gophermart +ACCRUAL=./cmd/accrual/accrual_linux_amd64 +PORT=8080 +ADDRESS=localhost +DSN='postgresql://postgres:postgres@localhost:5432/gophermart?sslmode=disable' + +.PHONY: build +build: + go test -v -cover ./... + go build -o ${APP} ./cmd/gophermart/... + +.PHONY: test +test: build + gophermarttest -test.v -test.run="^TestGophermart$$" \ + -gophermart-binary-path=${APP} \ + -gophermart-host=${ADDRESS} \ + -gophermart-port=${PORT} \ + -gophermart-database-uri=${DSN} \ + -accrual-binary-path=${ACCRUAL} \ + -accrual-host=${ADDRESS} \ + -accrual-port=34567 \ + -accrual-database-uri=${DSN} diff --git a/cmd/gophermart/main.go b/cmd/gophermart/main.go index 38dd16d..c638f17 100644 --- a/cmd/gophermart/main.go +++ b/cmd/gophermart/main.go @@ -1,3 +1,74 @@ package main -func main() {} +import ( + "fmt" + "net/http" + "time" + + "github.com/go-chi/chi/v5" + "github.com/go-resty/resty/v2" + + "github.com/OlegVankov/fantastic-engine/internal/handler" +) + +func main() { + + router := chi.NewRouter() + + router.Route("/api/user", func(r chi.Router) { + r.Post("/register", handler.Register) + r.Post("/login", handler.Login) + + r.Route("/", func(r chi.Router) { + r.Use(handler.Auth) + + r.Post("/orders", handler.Orders) + r.Get("/orders", handler.GetOrders) + + r.Post("/balance/withdraw", handler.Withdraw) + r.Get("/balance", handler.Balance) + r.Get("/withdrawals", handler.Withdrawals) + }) + }) + + go func() { + client := resty.New() + url := "http://localhost:34567/api/orders/" + ball := struct { + Order string `json:"order"` + Status string `json:"status"` + Accrual float64 `json:"accrual"` + }{} + for { + for k := range handler.Orders2 { + resp, err := client.R().SetResult(&ball).Get(url + k) + if err != nil { + fmt.Printf("[ERROR] %s\n", err.Error()) + } + + if resp.StatusCode() == http.StatusOK { + + username := handler.Orders2[ball.Order] + user := handler.Users2[username] + order := handler.Users2[username].Order[ball.Order] + order.Status = ball.Status + + if ball.Status == "PROCESSED" { + order.Accrual = ball.Accrual + user.Balance += ball.Accrual + } + + handler.Users2[username] = user + handler.Users2[username].Order[ball.Order] = order + + } + + } + + <-time.After(time.Second) + } + }() + + fmt.Println("start server port 8080") + http.ListenAndServe(":8080", router) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..4c67e2d --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module github.com/OlegVankov/fantastic-engine + +go 1.20 + +require ( + github.com/go-chi/chi/v5 v5.0.11 // indirect + github.com/go-resty/resty/v2 v2.11.0 // indirect + github.com/golang-jwt/jwt/v4 v4.5.0 // indirect + golang.org/x/net v0.19.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..9360743 --- /dev/null +++ b/go.sum @@ -0,0 +1,48 @@ +github.com/go-chi/chi/v5 v5.0.11 h1:BnpYbFZ3T3S1WMpD79r7R5ThWX40TaFB7L31Y8xqSwA= +github.com/go-chi/chi/v5 v5.0.11/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-resty/resty/v2 v2.11.0 h1:i7jMfNOJYMp69lq7qozJP+bjgzfAzeOhuGlyDrqxT/8= +github.com/go-resty/resty/v2 v2.11.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/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-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/handler/handler.go b/internal/handler/handler.go new file mode 100644 index 0000000..08be423 --- /dev/null +++ b/internal/handler/handler.go @@ -0,0 +1,303 @@ +package handler + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "sort" + "strconv" + "strings" + "time" + + "github.com/golang-jwt/jwt/v4" +) + +type User struct { + Login string `json:"login"` + Password string `json:"password"` + Token string + Balance float64 + Withdraw float64 + Order map[string]Order +} + +type Order struct { + Number string + Status string + Accrual float64 + Uploaded time.Time +} + +type UserClaim struct { + jwt.RegisteredClaims + Username string +} + +// [login] +var Users2 = map[string]User{} + +// [login] +var Orders2 = map[string]string{} + +func createToken(username string) (string, error) { + userClaim := &UserClaim{ + Username: username, + RegisteredClaims: jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24)), + }, + } + token := jwt.NewWithClaims(jwt.SigningMethodHS256, userClaim) + + tokenString, err := token.SignedString([]byte("secret_key")) + if err != nil { + return "", err + } + + return tokenString, nil +} + +func checkLun(num string) bool { + sum := 0 + parity := len(num) % 2 + + for i, v := range num { + digit, _ := strconv.Atoi(string(v)) + if i%2 == parity { + digit *= 2 + if digit > 9 { + digit = digit%10 + digit/10 + } + } + sum += digit + } + + return sum%10 == 0 +} + +func Register(w http.ResponseWriter, r *http.Request) { + + user := User{} + + err := json.NewDecoder(r.Body).Decode(&user) + + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + if _, ok := Users2[user.Login]; ok { + w.WriteHeader(http.StatusConflict) + return + } + + tkn, _ := createToken(user.Login) + authorization := fmt.Sprintf("Bearer %s", tkn) + + user.Token = tkn + user.Order = map[string]Order{} + Users2[user.Login] = user + + w.Header().Add("Authorization", authorization) + w.WriteHeader(http.StatusOK) +} + +func Login(w http.ResponseWriter, r *http.Request) { + user := User{} + + err := json.NewDecoder(r.Body).Decode(&user) + + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + if Users2[user.Login].Password != user.Password { + w.WriteHeader(http.StatusUnauthorized) + return + } + + tkn, _ := createToken(user.Login) + authorization := fmt.Sprintf("Bearer %s", tkn) + + user.Token = tkn + user.Order = map[string]Order{} + Users2[user.Login] = user + + w.Header().Add("Authorization", authorization) + w.WriteHeader(http.StatusOK) +} + +func Orders(w http.ResponseWriter, r *http.Request) { + + body, err := io.ReadAll(r.Body) + + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + fmt.Printf("[INFO] method: %s path: %s body: %q\n", r.Method, r.URL.Path, body) + + if r.Header.Get("Content-Type") != "text/plain" { + w.WriteHeader(http.StatusBadRequest) + } + + number := string(body) + + if !checkLun(number) { + w.WriteHeader(http.StatusUnprocessableEntity) + return + } + + username := r.Header.Get("username") + + if _, ok := Orders2[number]; ok { + if Orders2[number] == username { + w.WriteHeader(http.StatusOK) + return + } + w.WriteHeader(http.StatusConflict) + return + } + + Orders2[number] = username + + // user := Users2[username] + // user.Order = append(user.Order, Order{Number: number, Status: "NEW", Uploaded: time.Now()}) + // Users2[username] = user + + Users2[username].Order[number] = Order{Number: number, Status: "NEW", Uploaded: time.Now()} + + // fmt.Printf("[INFO] POST /api/user/Orders2 %s %s %v\n", username, number, Users2[username].Order) + + w.WriteHeader(http.StatusAccepted) +} + +func GetOrders(w http.ResponseWriter, r *http.Request) { + username := r.Header.Get("username") + o := []Order{} + for _, order := range Users2[username].Order { + o = append(o, order) + } + if len(o) == 0 { + w.WriteHeader(http.StatusNoContent) + return + } + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(o) +} + +func Withdraw(w http.ResponseWriter, r *http.Request) { + username := r.Header.Get("username") + withdraw := struct { + Order string + Sum float64 + }{} + err := json.NewDecoder(r.Body).Decode(&withdraw) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + user := Users2[username] + + if !checkLun(withdraw.Order) { + w.WriteHeader(http.StatusUnprocessableEntity) + return + } + + Orders2[withdraw.Order] = username + + if withdraw.Sum > user.Balance { + w.WriteHeader(http.StatusPaymentRequired) + return + } + + user.Balance -= withdraw.Sum + user.Withdraw += withdraw.Sum + user.Order[withdraw.Order] = Order{Number: withdraw.Order, Status: "NEW", Uploaded: time.Now()} + + Users2[username] = user + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) +} + +func Balance(w http.ResponseWriter, r *http.Request) { + username := r.Header.Get("username") + + balance := struct { + Current float64 + Withdrawn float64 + }{ + Current: Users2[username].Balance, + Withdrawn: Users2[username].Withdraw, + } + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(balance) +} + +func Withdrawals(w http.ResponseWriter, r *http.Request) { + username := r.Header.Get("username") + + type Wd struct { + Order string + Sum float64 + Proccessed_At time.Time + uploaded time.Time + } + withdrawals := []Wd{} + + for _, v := range Users2[username].Order { + withdrawals = append(withdrawals, Wd{Order: v.Number, Sum: Users2[username].Withdraw, Proccessed_At: time.Now(), uploaded: v.Uploaded}) + } + + sort.Slice(withdrawals, func(i, j int) bool { + return withdrawals[j].uploaded.Before(withdrawals[i].uploaded) + }) + + if len(withdrawals) == 0 { + w.WriteHeader(http.StatusNoContent) + return + } + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(withdrawals) +} + +func Auth(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + auth := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ") + + if auth == "" { + w.WriteHeader(http.StatusUnauthorized) + return + } + + userClaim := UserClaim{} + + token, err := jwt.ParseWithClaims(auth, &userClaim, func(token *jwt.Token) (interface{}, error) { return []byte("secret_key"), nil }) + + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + if !token.Valid { + w.WriteHeader(http.StatusUnauthorized) + return + } + + if _, ok := Users2[userClaim.Username]; !ok { + w.WriteHeader(http.StatusUnauthorized) + return + } + + r.Header.Add("username", userClaim.Username) + h.ServeHTTP(w, r) + }) +} From 9ee7c22969bb00f3eb90f83eebb8e647058062c5 Mon Sep 17 00:00:00 2001 From: oleg Date: Sat, 6 Jan 2024 13:14:48 +0300 Subject: [PATCH 02/18] add env configuration --- cmd/gophermart/main.go | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/cmd/gophermart/main.go b/cmd/gophermart/main.go index c638f17..0f893fe 100644 --- a/cmd/gophermart/main.go +++ b/cmd/gophermart/main.go @@ -1,8 +1,10 @@ package main import ( + "flag" "fmt" "net/http" + "os" "time" "github.com/go-chi/chi/v5" @@ -13,6 +15,23 @@ import ( func main() { + var ( + serverAddr string + accrualAddr string + ) + + flag.StringVar(&serverAddr, "a", "localhost:8080", "адрес и порт запуска сервиса") + flag.StringVar(&accrualAddr, "r", "localhost:34567", "адрес системы расчёта начислений") + + flag.Parse() + + if envRunAddr := os.Getenv("RUN_ADDRESS"); envRunAddr != "" { + serverAddr = envRunAddr + } + if envAccrualAddr := os.Getenv("ACCRUAL_SYSTEM_ADDRESS"); envAccrualAddr != "" { + accrualAddr = envAccrualAddr + } + router := chi.NewRouter() router.Route("/api/user", func(r chi.Router) { @@ -33,7 +52,7 @@ func main() { go func() { client := resty.New() - url := "http://localhost:34567/api/orders/" + url := accrualAddr + "/api/orders/" ball := struct { Order string `json:"order"` Status string `json:"status"` @@ -69,6 +88,6 @@ func main() { } }() - fmt.Println("start server port 8080") - http.ListenAndServe(":8080", router) + fmt.Println("start server:", serverAddr) + http.ListenAndServe(serverAddr, router) } From c99464aa31ec8c17a689195962b7bf288c9de419 Mon Sep 17 00:00:00 2001 From: oleg Date: Sat, 6 Jan 2024 16:33:04 +0300 Subject: [PATCH 03/18] refactor system accrual --- cmd/gophermart/main.go | 41 ++----------------------------- internal/accrual/accrual.go | 49 +++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 39 deletions(-) create mode 100644 internal/accrual/accrual.go diff --git a/cmd/gophermart/main.go b/cmd/gophermart/main.go index 0f893fe..38acd37 100644 --- a/cmd/gophermart/main.go +++ b/cmd/gophermart/main.go @@ -5,11 +5,10 @@ import ( "fmt" "net/http" "os" - "time" "github.com/go-chi/chi/v5" - "github.com/go-resty/resty/v2" + "github.com/OlegVankov/fantastic-engine/internal/accrual" "github.com/OlegVankov/fantastic-engine/internal/handler" ) @@ -50,43 +49,7 @@ func main() { }) }) - go func() { - client := resty.New() - url := accrualAddr + "/api/orders/" - ball := struct { - Order string `json:"order"` - Status string `json:"status"` - Accrual float64 `json:"accrual"` - }{} - for { - for k := range handler.Orders2 { - resp, err := client.R().SetResult(&ball).Get(url + k) - if err != nil { - fmt.Printf("[ERROR] %s\n", err.Error()) - } - - if resp.StatusCode() == http.StatusOK { - - username := handler.Orders2[ball.Order] - user := handler.Users2[username] - order := handler.Users2[username].Order[ball.Order] - order.Status = ball.Status - - if ball.Status == "PROCESSED" { - order.Accrual = ball.Accrual - user.Balance += ball.Accrual - } - - handler.Users2[username] = user - handler.Users2[username].Order[ball.Order] = order - - } - - } - - <-time.After(time.Second) - } - }() + go accrual.SendAccrual(accrualAddr) fmt.Println("start server:", serverAddr) http.ListenAndServe(serverAddr, router) diff --git a/internal/accrual/accrual.go b/internal/accrual/accrual.go new file mode 100644 index 0000000..572d2a5 --- /dev/null +++ b/internal/accrual/accrual.go @@ -0,0 +1,49 @@ +package accrual + +import ( + "fmt" + "net/http" + "time" + + "github.com/go-resty/resty/v2" + + "github.com/OlegVankov/fantastic-engine/internal/handler" +) + +func SendAccrual(addr string) { + client := resty.New() + url := addr + "/api/orders/" + ball := struct { + Order string `json:"order"` + Status string `json:"status"` + Accrual float64 `json:"accrual"` + }{} + for { + for k := range handler.Orders2 { + resp, err := client.R().SetResult(&ball).Get(url + k) + if err != nil { + fmt.Printf("[ERROR] %s\n", err.Error()) + } + + if resp.StatusCode() == http.StatusOK { + + username := handler.Orders2[ball.Order] + user := handler.Users2[username] + order := handler.Users2[username].Order[ball.Order] + order.Status = ball.Status + + if ball.Status == "PROCESSED" { + order.Accrual = ball.Accrual + user.Balance += ball.Accrual + } + + handler.Users2[username] = user + handler.Users2[username].Order[ball.Order] = order + + } + + } + + <-time.After(time.Second) + } +} From dda553fb48a318a56e798363219a056c1fb6f7e1 Mon Sep 17 00:00:00 2001 From: oleg Date: Sat, 6 Jan 2024 17:44:30 +0300 Subject: [PATCH 04/18] refactor util --- internal/handler/handler.go | 72 +++++++++---------------------------- internal/util/jwt.go | 29 +++++++++++++++ internal/util/lun.go | 21 +++++++++++ 3 files changed, 67 insertions(+), 55 deletions(-) create mode 100644 internal/util/jwt.go create mode 100644 internal/util/lun.go diff --git a/internal/handler/handler.go b/internal/handler/handler.go index 08be423..542eb30 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -6,11 +6,12 @@ import ( "io" "net/http" "sort" - "strconv" "strings" "time" "github.com/golang-jwt/jwt/v4" + + "github.com/OlegVankov/fantastic-engine/internal/util" ) type User struct { @@ -29,52 +30,12 @@ type Order struct { Uploaded time.Time } -type UserClaim struct { - jwt.RegisteredClaims - Username string -} - // [login] var Users2 = map[string]User{} // [login] var Orders2 = map[string]string{} -func createToken(username string) (string, error) { - userClaim := &UserClaim{ - Username: username, - RegisteredClaims: jwt.RegisteredClaims{ - ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24)), - }, - } - token := jwt.NewWithClaims(jwt.SigningMethodHS256, userClaim) - - tokenString, err := token.SignedString([]byte("secret_key")) - if err != nil { - return "", err - } - - return tokenString, nil -} - -func checkLun(num string) bool { - sum := 0 - parity := len(num) % 2 - - for i, v := range num { - digit, _ := strconv.Atoi(string(v)) - if i%2 == parity { - digit *= 2 - if digit > 9 { - digit = digit%10 + digit/10 - } - } - sum += digit - } - - return sum%10 == 0 -} - func Register(w http.ResponseWriter, r *http.Request) { user := User{} @@ -91,7 +52,7 @@ func Register(w http.ResponseWriter, r *http.Request) { return } - tkn, _ := createToken(user.Login) + tkn, _ := util.CreateToken(user.Login) authorization := fmt.Sprintf("Bearer %s", tkn) user.Token = tkn @@ -117,7 +78,7 @@ func Login(w http.ResponseWriter, r *http.Request) { return } - tkn, _ := createToken(user.Login) + tkn, _ := util.CreateToken(user.Login) authorization := fmt.Sprintf("Bearer %s", tkn) user.Token = tkn @@ -136,7 +97,6 @@ func Orders(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) return } - fmt.Printf("[INFO] method: %s path: %s body: %q\n", r.Method, r.URL.Path, body) if r.Header.Get("Content-Type") != "text/plain" { w.WriteHeader(http.StatusBadRequest) @@ -144,7 +104,7 @@ func Orders(w http.ResponseWriter, r *http.Request) { number := string(body) - if !checkLun(number) { + if !util.CheckLun(number) { w.WriteHeader(http.StatusUnprocessableEntity) return } @@ -162,14 +122,8 @@ func Orders(w http.ResponseWriter, r *http.Request) { Orders2[number] = username - // user := Users2[username] - // user.Order = append(user.Order, Order{Number: number, Status: "NEW", Uploaded: time.Now()}) - // Users2[username] = user - Users2[username].Order[number] = Order{Number: number, Status: "NEW", Uploaded: time.Now()} - // fmt.Printf("[INFO] POST /api/user/Orders2 %s %s %v\n", username, number, Users2[username].Order) - w.WriteHeader(http.StatusAccepted) } @@ -202,7 +156,7 @@ func Withdraw(w http.ResponseWriter, r *http.Request) { user := Users2[username] - if !checkLun(withdraw.Order) { + if !util.CheckLun(withdraw.Order) { w.WriteHeader(http.StatusUnprocessableEntity) return } @@ -252,7 +206,13 @@ func Withdrawals(w http.ResponseWriter, r *http.Request) { withdrawals := []Wd{} for _, v := range Users2[username].Order { - withdrawals = append(withdrawals, Wd{Order: v.Number, Sum: Users2[username].Withdraw, Proccessed_At: time.Now(), uploaded: v.Uploaded}) + withdrawals = append(withdrawals, + Wd{ + Order: v.Number, + Sum: Users2[username].Withdraw, + Proccessed_At: time.Now(), + uploaded: v.Uploaded, + }) } sort.Slice(withdrawals, func(i, j int) bool { @@ -278,9 +238,11 @@ func Auth(h http.Handler) http.Handler { return } - userClaim := UserClaim{} + userClaim := util.UserClaim{} - token, err := jwt.ParseWithClaims(auth, &userClaim, func(token *jwt.Token) (interface{}, error) { return []byte("secret_key"), nil }) + token, err := jwt.ParseWithClaims(auth, &userClaim, func(token *jwt.Token) (interface{}, error) { + return []byte("secret_key"), nil + }) if err != nil { w.WriteHeader(http.StatusInternalServerError) diff --git a/internal/util/jwt.go b/internal/util/jwt.go new file mode 100644 index 0000000..c66752d --- /dev/null +++ b/internal/util/jwt.go @@ -0,0 +1,29 @@ +package util + +import ( + "time" + + "github.com/golang-jwt/jwt/v4" +) + +type UserClaim struct { + jwt.RegisteredClaims + Username string +} + +func CreateToken(username string) (string, error) { + userClaim := &UserClaim{ + Username: username, + RegisteredClaims: jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24)), + }, + } + token := jwt.NewWithClaims(jwt.SigningMethodHS256, userClaim) + + tokenString, err := token.SignedString([]byte("secret_key")) + if err != nil { + return "", err + } + + return tokenString, nil +} diff --git a/internal/util/lun.go b/internal/util/lun.go new file mode 100644 index 0000000..a7e2a7b --- /dev/null +++ b/internal/util/lun.go @@ -0,0 +1,21 @@ +package util + +import "strconv" + +func CheckLun(num string) bool { + sum := 0 + parity := len(num) % 2 + + for i, v := range num { + digit, _ := strconv.Atoi(string(v)) + if i%2 == parity { + digit *= 2 + if digit > 9 { + digit = digit%10 + digit/10 + } + } + sum += digit + } + + return sum%10 == 0 +} From aee74e14491d5e2da1e765c8293c1471cc826139 Mon Sep 17 00:00:00 2001 From: oleg Date: Sat, 6 Jan 2024 17:47:32 +0300 Subject: [PATCH 05/18] refactor midlleware --- internal/handler/checkauth.go | 45 +++++++++++++++++++++++++++++++++++ internal/handler/handler.go | 38 ----------------------------- 2 files changed, 45 insertions(+), 38 deletions(-) create mode 100644 internal/handler/checkauth.go diff --git a/internal/handler/checkauth.go b/internal/handler/checkauth.go new file mode 100644 index 0000000..a014ca9 --- /dev/null +++ b/internal/handler/checkauth.go @@ -0,0 +1,45 @@ +package handler + +import ( + "net/http" + "strings" + + "github.com/golang-jwt/jwt/v4" + + "github.com/OlegVankov/fantastic-engine/internal/util" +) + +func Auth(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + auth := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ") + + if auth == "" { + w.WriteHeader(http.StatusUnauthorized) + return + } + + userClaim := util.UserClaim{} + + token, err := jwt.ParseWithClaims(auth, &userClaim, func(token *jwt.Token) (interface{}, error) { + return []byte("secret_key"), nil + }) + + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + if !token.Valid { + w.WriteHeader(http.StatusUnauthorized) + return + } + + if _, ok := Users2[userClaim.Username]; !ok { + w.WriteHeader(http.StatusUnauthorized) + return + } + + r.Header.Add("username", userClaim.Username) + h.ServeHTTP(w, r) + }) +} diff --git a/internal/handler/handler.go b/internal/handler/handler.go index 542eb30..cf3186b 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -6,11 +6,8 @@ import ( "io" "net/http" "sort" - "strings" "time" - "github.com/golang-jwt/jwt/v4" - "github.com/OlegVankov/fantastic-engine/internal/util" ) @@ -228,38 +225,3 @@ func Withdrawals(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(withdrawals) } - -func Auth(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - auth := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ") - - if auth == "" { - w.WriteHeader(http.StatusUnauthorized) - return - } - - userClaim := util.UserClaim{} - - token, err := jwt.ParseWithClaims(auth, &userClaim, func(token *jwt.Token) (interface{}, error) { - return []byte("secret_key"), nil - }) - - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - return - } - - if !token.Valid { - w.WriteHeader(http.StatusUnauthorized) - return - } - - if _, ok := Users2[userClaim.Username]; !ok { - w.WriteHeader(http.StatusUnauthorized) - return - } - - r.Header.Add("username", userClaim.Username) - h.ServeHTTP(w, r) - }) -} From 498c1a8c31b0dca7e937404342c3e62b1bb58c7c Mon Sep 17 00:00:00 2001 From: oleg Date: Tue, 9 Jan 2024 23:12:57 +0300 Subject: [PATCH 06/18] add database --- cmd/gophermart/main.go | 28 ++- go.mod | 8 + go.sum | 26 +++ internal/accrual/accrual.go | 49 ----- internal/handler/accrual/accrual.go | 52 +++++ internal/handler/checkauth.go | 7 +- internal/handler/handler.go | 177 +++++++++-------- internal/model/models.go | 27 +++ internal/repository/postgres/user.go | 282 +++++++++++++++++++++++++++ internal/repository/repository.go | 20 ++ internal/util/jwt.go | 4 +- 11 files changed, 530 insertions(+), 150 deletions(-) delete mode 100644 internal/accrual/accrual.go create mode 100644 internal/handler/accrual/accrual.go create mode 100644 internal/model/models.go create mode 100644 internal/repository/postgres/user.go create mode 100644 internal/repository/repository.go diff --git a/cmd/gophermart/main.go b/cmd/gophermart/main.go index 38acd37..eed23fd 100644 --- a/cmd/gophermart/main.go +++ b/cmd/gophermart/main.go @@ -2,25 +2,28 @@ package main import ( "flag" - "fmt" + "log" "net/http" "os" "github.com/go-chi/chi/v5" + _ "github.com/jackc/pgx/v5/stdlib" - "github.com/OlegVankov/fantastic-engine/internal/accrual" "github.com/OlegVankov/fantastic-engine/internal/handler" + "github.com/OlegVankov/fantastic-engine/internal/handler/accrual" ) -func main() { +var ( + serverAddr string + accrualAddr string + databaseURI string +) - var ( - serverAddr string - accrualAddr string - ) +func main() { flag.StringVar(&serverAddr, "a", "localhost:8080", "адрес и порт запуска сервиса") - flag.StringVar(&accrualAddr, "r", "localhost:34567", "адрес системы расчёта начислений") + flag.StringVar(&accrualAddr, "r", "http://localhost:34567", "адрес системы расчёта начислений") + flag.StringVar(&databaseURI, "d", "", "адрес подключения к базе данных") flag.Parse() @@ -30,6 +33,9 @@ func main() { if envAccrualAddr := os.Getenv("ACCRUAL_SYSTEM_ADDRESS"); envAccrualAddr != "" { accrualAddr = envAccrualAddr } + if envDatabaseURI := os.Getenv("DATABASE_URI"); envDatabaseURI != "" { + databaseURI = envDatabaseURI + } router := chi.NewRouter() @@ -49,8 +55,12 @@ func main() { }) }) + err := handler.SetRepository(databaseURI) + if err != nil { + log.Fatal(err) + } + go accrual.SendAccrual(accrualAddr) - fmt.Println("start server:", serverAddr) http.ListenAndServe(serverAddr, router) } diff --git a/go.mod b/go.mod index 4c67e2d..0e0d161 100644 --- a/go.mod +++ b/go.mod @@ -6,5 +6,13 @@ require ( github.com/go-chi/chi/v5 v5.0.11 // indirect github.com/go-resty/resty/v2 v2.11.0 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect + github.com/jackc/pgx/v5 v5.5.1 // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect + github.com/jmoiron/sqlx v1.3.5 // indirect + golang.org/x/crypto v0.18.0 // indirect golang.org/x/net v0.19.0 // indirect + golang.org/x/sync v0.6.0 // indirect + golang.org/x/text v0.14.0 // indirect ) diff --git a/go.sum b/go.sum index 9360743..8bd9c19 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,33 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-chi/chi/v5 v5.0.11 h1:BnpYbFZ3T3S1WMpD79r7R5ThWX40TaFB7L31Y8xqSwA= github.com/go-chi/chi/v5 v5.0.11/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-resty/resty/v2 v2.11.0 h1:i7jMfNOJYMp69lq7qozJP+bjgzfAzeOhuGlyDrqxT/8= github.com/go-resty/resty/v2 v2.11.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA= +github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.5.1 h1:5I9etrGkLrN+2XPCsi6XLlV5DITbSL/xBZdmAxFcXPI= +github.com/jackc/pgx/v5 v5.5.1/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= +github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -21,6 +41,8 @@ golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -40,9 +62,13 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/accrual/accrual.go b/internal/accrual/accrual.go deleted file mode 100644 index 572d2a5..0000000 --- a/internal/accrual/accrual.go +++ /dev/null @@ -1,49 +0,0 @@ -package accrual - -import ( - "fmt" - "net/http" - "time" - - "github.com/go-resty/resty/v2" - - "github.com/OlegVankov/fantastic-engine/internal/handler" -) - -func SendAccrual(addr string) { - client := resty.New() - url := addr + "/api/orders/" - ball := struct { - Order string `json:"order"` - Status string `json:"status"` - Accrual float64 `json:"accrual"` - }{} - for { - for k := range handler.Orders2 { - resp, err := client.R().SetResult(&ball).Get(url + k) - if err != nil { - fmt.Printf("[ERROR] %s\n", err.Error()) - } - - if resp.StatusCode() == http.StatusOK { - - username := handler.Orders2[ball.Order] - user := handler.Users2[username] - order := handler.Users2[username].Order[ball.Order] - order.Status = ball.Status - - if ball.Status == "PROCESSED" { - order.Accrual = ball.Accrual - user.Balance += ball.Accrual - } - - handler.Users2[username] = user - handler.Users2[username].Order[ball.Order] = order - - } - - } - - <-time.After(time.Second) - } -} diff --git a/internal/handler/accrual/accrual.go b/internal/handler/accrual/accrual.go new file mode 100644 index 0000000..6c4f68d --- /dev/null +++ b/internal/handler/accrual/accrual.go @@ -0,0 +1,52 @@ +package accrual + +import ( + "context" + "fmt" + "net/http" + "time" + + "github.com/go-resty/resty/v2" + + "github.com/OlegVankov/fantastic-engine/internal/handler" +) + +func SendAccrual(addr string) { + client := resty.New() + url := addr + "/api/orders/" + ball := struct { + Order string `json:"order"` + Status string `json:"status"` + Accrual float64 `json:"accrual"` + }{} + for { + + orders, err := handler.Repository.GetOrders(context.Background()) + if err != nil { + continue + } + + for _, k := range orders { + url := url + k.Number + resp, err := client.R(). + SetResult(&ball). + Get(url) + if err != nil { + fmt.Printf("[ERROR] %s\n", err.Error()) + } + + if resp.StatusCode() == http.StatusOK && ball.Status == "PROCESSED" { + + err := handler.Repository.UpdateOrder(context.Background(), ball.Order, ball.Status, ball.Accrual) + if err != nil { + fmt.Printf("[ERROR] %s\n", err.Error()) + continue + } + + } + + } + + <-time.After(time.Second * time.Duration(5)) + } +} diff --git a/internal/handler/checkauth.go b/internal/handler/checkauth.go index a014ca9..760f099 100644 --- a/internal/handler/checkauth.go +++ b/internal/handler/checkauth.go @@ -1,6 +1,7 @@ package handler import ( + "fmt" "net/http" "strings" @@ -30,11 +31,7 @@ func Auth(h http.Handler) http.Handler { } if !token.Valid { - w.WriteHeader(http.StatusUnauthorized) - return - } - - if _, ok := Users2[userClaim.Username]; !ok { + fmt.Println("token not valid", userClaim.UserID, userClaim.Username) w.WriteHeader(http.StatusUnauthorized) return } diff --git a/internal/handler/handler.go b/internal/handler/handler.go index cf3186b..09b1ef2 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -1,87 +1,98 @@ package handler import ( + "context" "encoding/json" + "errors" "fmt" "io" "net/http" - "sort" - "time" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" + + "github.com/OlegVankov/fantastic-engine/internal/repository" + "github.com/OlegVankov/fantastic-engine/internal/repository/postgres" "github.com/OlegVankov/fantastic-engine/internal/util" ) -type User struct { +type credential struct { Login string `json:"login"` Password string `json:"password"` - Token string - Balance float64 - Withdraw float64 - Order map[string]Order -} - -type Order struct { - Number string - Status string - Accrual float64 - Uploaded time.Time } -// [login] -var Users2 = map[string]User{} +var ( + Repository repository.Repository + // = postgres.NewUserRepository("postgresql://postgres:postgres@localhost:5432/gophermart?sslmode=disable") +) -// [login] -var Orders2 = map[string]string{} +func SetRepository(dsn string) error { + Repository = postgres.NewUserRepository(dsn) + return nil +} func Register(w http.ResponseWriter, r *http.Request) { - user := User{} + c := credential{} - err := json.NewDecoder(r.Body).Decode(&user) + err := json.NewDecoder(r.Body).Decode(&c) if err != nil { w.WriteHeader(http.StatusBadRequest) return } - if _, ok := Users2[user.Login]; ok { - w.WriteHeader(http.StatusConflict) + user, err := Repository.AddUser(context.Background(), c.Login, c.Password) + if err != nil { + var e *pgconn.PgError + if errors.As(err, &e) && e.Code == "23505" { + w.WriteHeader(http.StatusConflict) + return + } + fmt.Printf("%v\n", err) + w.WriteHeader(http.StatusInternalServerError) return } - tkn, _ := util.CreateToken(user.Login) + tkn, err := util.CreateToken(user.Login, user.ID) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } authorization := fmt.Sprintf("Bearer %s", tkn) - user.Token = tkn - user.Order = map[string]Order{} - Users2[user.Login] = user - w.Header().Add("Authorization", authorization) w.WriteHeader(http.StatusOK) } func Login(w http.ResponseWriter, r *http.Request) { - user := User{} + c := credential{} - err := json.NewDecoder(r.Body).Decode(&user) + err := json.NewDecoder(r.Body).Decode(&c) if err != nil { w.WriteHeader(http.StatusBadRequest) return } - if Users2[user.Login].Password != user.Password { + user, err := Repository.GetUser(context.Background(), c.Login) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + w.WriteHeader(http.StatusUnauthorized) + return + } + w.WriteHeader(http.StatusInternalServerError) + return + } + + if user.Password != c.Password { w.WriteHeader(http.StatusUnauthorized) return } - tkn, _ := util.CreateToken(user.Login) + tkn, _ := util.CreateToken(user.Login, user.ID) authorization := fmt.Sprintf("Bearer %s", tkn) - user.Token = tkn - user.Order = map[string]Order{} - Users2[user.Login] = user - w.Header().Add("Authorization", authorization) w.WriteHeader(http.StatusOK) } @@ -108,35 +119,45 @@ func Orders(w http.ResponseWriter, r *http.Request) { username := r.Header.Get("username") - if _, ok := Orders2[number]; ok { - if Orders2[number] == username { - w.WriteHeader(http.StatusOK) - return + _, err = Repository.AddOrder(context.Background(), username, number) + if err != nil { + var e *pgconn.PgError + if errors.As(err, &e) && e.Code == "23505" { + order, err := Repository.GetOrderByNumber(context.Background(), number) + // fmt.Printf("username: %s number %s %v\n", username, number, order) + if err == nil { + if order.UserLogin == username { + w.WriteHeader(http.StatusOK) + return + } + w.WriteHeader(http.StatusConflict) + return + } } - w.WriteHeader(http.StatusConflict) + w.WriteHeader(http.StatusInternalServerError) return } - Orders2[number] = username - - Users2[username].Order[number] = Order{Number: number, Status: "NEW", Uploaded: time.Now()} - w.WriteHeader(http.StatusAccepted) } func GetOrders(w http.ResponseWriter, r *http.Request) { username := r.Header.Get("username") - o := []Order{} - for _, order := range Users2[username].Order { - o = append(o, order) + + orders, err := Repository.GetOrdersByLogin(context.Background(), username) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return } - if len(o) == 0 { + + if len(orders) == 0 { w.WriteHeader(http.StatusNoContent) return } + w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(o) + json.NewEncoder(w).Encode(orders) } func Withdraw(w http.ResponseWriter, r *http.Request) { @@ -151,26 +172,21 @@ func Withdraw(w http.ResponseWriter, r *http.Request) { return } - user := Users2[username] - if !util.CheckLun(withdraw.Order) { w.WriteHeader(http.StatusUnprocessableEntity) return } - Orders2[withdraw.Order] = username - - if withdraw.Sum > user.Balance { - w.WriteHeader(http.StatusPaymentRequired) + err = Repository.UpdateWithdraw(context.Background(), username, withdraw.Order, withdraw.Sum) + if err != nil { + if err.Error() == "balance error" { + w.WriteHeader(http.StatusPaymentRequired) + return + } + w.WriteHeader(http.StatusInternalServerError) return } - user.Balance -= withdraw.Sum - user.Withdraw += withdraw.Sum - user.Order[withdraw.Order] = Order{Number: withdraw.Order, Status: "NEW", Uploaded: time.Now()} - - Users2[username] = user - w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) } @@ -178,12 +194,18 @@ func Withdraw(w http.ResponseWriter, r *http.Request) { func Balance(w http.ResponseWriter, r *http.Request) { username := r.Header.Get("username") + user, err := Repository.GetBalance(context.Background(), username) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + balance := struct { Current float64 Withdrawn float64 }{ - Current: Users2[username].Balance, - Withdrawn: Users2[username].Withdraw, + user.Balance, + user.Withdraw, } w.Header().Add("Content-Type", "application/json") @@ -194,34 +216,17 @@ func Balance(w http.ResponseWriter, r *http.Request) { func Withdrawals(w http.ResponseWriter, r *http.Request) { username := r.Header.Get("username") - type Wd struct { - Order string - Sum float64 - Proccessed_At time.Time - uploaded time.Time - } - withdrawals := []Wd{} - - for _, v := range Users2[username].Order { - withdrawals = append(withdrawals, - Wd{ - Order: v.Number, - Sum: Users2[username].Withdraw, - Proccessed_At: time.Now(), - uploaded: v.Uploaded, - }) + wd, err := Repository.GetWithdrawals(r.Context(), username) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return } - - sort.Slice(withdrawals, func(i, j int) bool { - return withdrawals[j].uploaded.Before(withdrawals[i].uploaded) - }) - - if len(withdrawals) == 0 { + if len(wd) == 0 { w.WriteHeader(http.StatusNoContent) return } w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(withdrawals) + json.NewEncoder(w).Encode(wd) } diff --git a/internal/model/models.go b/internal/model/models.go new file mode 100644 index 0000000..2287039 --- /dev/null +++ b/internal/model/models.go @@ -0,0 +1,27 @@ +package model + +import "time" + +type User struct { + ID uint64 `db:"id" json:"-"` + Login string `db:"login" json:"login"` + Password string `db:"password" json:"-"` + Balance float64 `db:"balance"` + Withdraw float64 `db:"withdraw"` +} + +type Order struct { + Number string + Status string `json:"status"` + Accrual float64 `json:"accrual"` + UserLogin string `json:"-"` + Uploaded time.Time `db:"uploaded" json:"uploaded_at"` +} + +type Withdraw struct { + ID uint64 `db:"id" json:"-"` + Number string `db:"number" json:"order"` + Amount float64 `db:"amount" json:"sum"` + UserLogin string `db:"userlogin" json:"-"` + ProcessedAt time.Time `db:"processed" json:"processed_at"` +} diff --git a/internal/repository/postgres/user.go b/internal/repository/postgres/user.go new file mode 100644 index 0000000..eb8eb47 --- /dev/null +++ b/internal/repository/postgres/user.go @@ -0,0 +1,282 @@ +package postgres + +import ( + "context" + "errors" + "fmt" + "log" + + "github.com/jmoiron/sqlx" + + "github.com/OlegVankov/fantastic-engine/internal/model" +) + +type UserRepository struct { + db *sqlx.DB +} + +func NewUserRepository(dsn string) *UserRepository { + db, _ := sqlx.Open("pgx", dsn) + repo := &UserRepository{ + db: db, + } + err := repo.Bootstrap(context.Background()) + if err != nil { + log.Fatal(err) + } + return repo +} + +func (r *UserRepository) Bootstrap(ctx context.Context) error { + users := `CREATE TABLE IF NOT EXISTS users +( + id bigserial PRIMARY KEY, + login varchar not null, + password varchar not null, + balance decimal(10, 2) default 0, + withdraw decimal(10, 2) default 0, + constraint users_unique_login unique (login) +); +` + orders := `CREATE TABLE IF NOT EXISTS orders +( + number varchar PRIMARY KEY not null, + status varchar, + accrual decimal(10, 2) default 0, + userlogin varchar not null, + uploaded timestamp with time zone not null default now(), + constraint orders_unique_number unique (number) +); +` + withdraw := `CREATE TABLE IF NOT EXISTS withdraw +( + id bigserial PRIMARY KEY, + number varchar not null, + amount decimal(10, 2) default 0, + userlogin varchar not null, + processed timestamp with time zone not null default now() +); +` + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return err + } + defer tx.Rollback() + + _, err = r.db.ExecContext(ctx, users) + if err != nil { + return err + } + + _, err = r.db.ExecContext(ctx, orders) + if err != nil { + return err + } + + _, err = r.db.ExecContext(ctx, withdraw) + if err != nil { + return err + } + + return tx.Commit() +} + +func (r *UserRepository) AddUser(ctx context.Context, login, password string) (*model.User, error) { + user := &model.User{} + err := r.db.QueryRowContext(ctx, + "insert into users(login, password) values($1, $2) returning *;", + login, + password, + ).Scan( + &user.ID, + &user.Login, + &user.Password, + &user.Balance, + &user.Withdraw, + ) + if err != nil { + return nil, err + } + return user, nil +} + +func (r *UserRepository) GetUser(ctx context.Context, login string) (*model.User, error) { + user := &model.User{} + err := r.db.QueryRowContext(ctx, + "select * from users where login = $1", + login, + ).Scan( + &user.ID, + &user.Login, + &user.Password, + &user.Balance, + &user.Withdraw, + ) + if err != nil { + return nil, err + } + return user, nil +} + +func (r *UserRepository) AddOrder(ctx context.Context, login, number string) (*model.Order, error) { + order := &model.Order{} + err := r.db.QueryRowContext(ctx, + "insert into orders(number, userlogin, status) values($1, $2, $3) returning *;", + number, + login, + "NEW", + ).Scan( + &order.Number, + &order.Status, + &order.Accrual, + &order.UserLogin, + &order.Uploaded, + ) + if err != nil { + return nil, err + } + return order, nil +} + +func (r *UserRepository) GetOrderByNumber(ctx context.Context, number string) (*model.Order, error) { + order := &model.Order{} + err := r.db.QueryRowContext(ctx, + "select number, userlogin from orders where number = $1", + number, + ).Scan( + &order.Number, + &order.UserLogin, + ) + if err != nil { + return nil, err + } + return order, nil +} + +func (r *UserRepository) UpdateOrder(ctx context.Context, number, status string, accrual float64) error { + order := &model.Order{} + + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return err + } + defer tx.Rollback() + + err = tx.QueryRow("update orders set status = $1, accrual = $2 where number = $3 returning number, userlogin", + status, + accrual, + number, + ).Scan( + &order.Number, + &order.UserLogin, + ) + + if err != nil { + return err + } + + _, err = tx.Exec("update users set balance = balance + $1 where login = $2", + accrual, + order.UserLogin, + ) + + if err != nil { + return err + } + + return tx.Commit() +} + +func (r *UserRepository) GetOrdersByLogin(ctx context.Context, username string) ([]model.Order, error) { + orders := []model.Order{} + err := r.db.SelectContext(ctx, &orders, "select * from orders where userlogin = $1", username) + if err != nil { + return nil, err + } + return orders, nil +} + +func (r *UserRepository) GetBalance(ctx context.Context, username string) (*model.User, error) { + user := &model.User{} + err := r.db.QueryRowContext(ctx, + "select balance, withdraw from users where login = $1", + username, + ).Scan( + &user.Balance, + &user.Withdraw, + ) + if err != nil { + return nil, err + } + return user, nil +} + +func (r *UserRepository) GetOrders(ctx context.Context) ([]model.Order, error) { + orders := []model.Order{} + err := r.db.SelectContext(ctx, &orders, "select * from orders") + if err != nil { + return nil, err + } + return orders, nil +} + +func (r *UserRepository) UpdateWithdraw(ctx context.Context, login, number string, sum float64) error { + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return err + } + defer tx.Rollback() + + order := &model.Order{} + balance := 0.0 + tx.QueryRow("select balance from users where login = $1", login).Scan(&balance) + + if balance < sum { + return errors.New("balance error") + } + + _, err = tx.Exec("update users set balance = balance - $1, withdraw = withdraw + $1 where login = $2", + sum, + login, + ) + if err != nil { + return err + } + + err = tx.QueryRowContext(ctx, + "insert into orders(number, userlogin, status) values($1, $2, $3) returning *;", + number, + login, + "NEW", + ).Scan( + &order.Number, + &order.Status, + &order.Accrual, + &order.UserLogin, + &order.Uploaded, + ) + if err != nil { + return err + } + + _, err = tx.Exec("insert into withdraw(number, amount, userlogin) values($1, $2, $3);", + number, + sum, + login, + ) + if err != nil { + fmt.Println(err) + return err + } + + return tx.Commit() +} + +func (r *UserRepository) GetWithdrawals(ctx context.Context, login string) ([]model.Withdraw, error) { + withdrawals := []model.Withdraw{} + err := r.db.SelectContext(ctx, &withdrawals, "select * from withdraw where userlogin = $1 order by processed;", login) + if err != nil { + return nil, err + } + return withdrawals, nil +} diff --git a/internal/repository/repository.go b/internal/repository/repository.go new file mode 100644 index 0000000..0062505 --- /dev/null +++ b/internal/repository/repository.go @@ -0,0 +1,20 @@ +package repository + +import ( + "context" + + "github.com/OlegVankov/fantastic-engine/internal/model" +) + +type Repository interface { + AddUser(ctx context.Context, login, password string) (*model.User, error) + GetUser(ctx context.Context, login string) (*model.User, error) + AddOrder(ctx context.Context, login, number string) (*model.Order, error) + GetOrdersByLogin(ctx context.Context, number string) ([]model.Order, error) + GetOrders(ctx context.Context) ([]model.Order, error) + GetOrderByNumber(ctx context.Context, number string) (*model.Order, error) + UpdateOrder(ctx context.Context, number, status string, accrual float64) error + GetBalance(ctx context.Context, username string) (*model.User, error) + UpdateWithdraw(ctx context.Context, login, number string, sum float64) error + GetWithdrawals(ctx context.Context, login string) ([]model.Withdraw, error) +} diff --git a/internal/util/jwt.go b/internal/util/jwt.go index c66752d..b6017f0 100644 --- a/internal/util/jwt.go +++ b/internal/util/jwt.go @@ -9,11 +9,13 @@ import ( type UserClaim struct { jwt.RegisteredClaims Username string + UserID uint64 } -func CreateToken(username string) (string, error) { +func CreateToken(username string, userid uint64) (string, error) { userClaim := &UserClaim{ Username: username, + UserID: userid, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24)), }, From 00280579ee6227c3fa489b881d83902433ffd86c Mon Sep 17 00:00:00 2001 From: oleg Date: Wed, 10 Jan 2024 14:00:32 +0300 Subject: [PATCH 07/18] refactoring handler --- cmd/gophermart/main.go | 34 ++--------------- internal/handler/accrual/accrual.go | 2 +- internal/handler/handler.go | 46 ++++++++++------------- internal/server.go | 57 +++++++++++++++++++++++++++++ 4 files changed, 80 insertions(+), 59 deletions(-) create mode 100644 internal/server.go diff --git a/cmd/gophermart/main.go b/cmd/gophermart/main.go index eed23fd..c69e398 100644 --- a/cmd/gophermart/main.go +++ b/cmd/gophermart/main.go @@ -2,15 +2,11 @@ package main import ( "flag" - "log" - "net/http" "os" - "github.com/go-chi/chi/v5" _ "github.com/jackc/pgx/v5/stdlib" - "github.com/OlegVankov/fantastic-engine/internal/handler" - "github.com/OlegVankov/fantastic-engine/internal/handler/accrual" + "github.com/OlegVankov/fantastic-engine/internal" ) var ( @@ -37,30 +33,6 @@ func main() { databaseURI = envDatabaseURI } - router := chi.NewRouter() - - router.Route("/api/user", func(r chi.Router) { - r.Post("/register", handler.Register) - r.Post("/login", handler.Login) - - r.Route("/", func(r chi.Router) { - r.Use(handler.Auth) - - r.Post("/orders", handler.Orders) - r.Get("/orders", handler.GetOrders) - - r.Post("/balance/withdraw", handler.Withdraw) - r.Get("/balance", handler.Balance) - r.Get("/withdrawals", handler.Withdrawals) - }) - }) - - err := handler.SetRepository(databaseURI) - if err != nil { - log.Fatal(err) - } - - go accrual.SendAccrual(accrualAddr) - - http.ListenAndServe(serverAddr, router) + server := internal.NewServer(serverAddr, databaseURI) + server.Run(accrualAddr) } diff --git a/internal/handler/accrual/accrual.go b/internal/handler/accrual/accrual.go index 6c4f68d..e43a25f 100644 --- a/internal/handler/accrual/accrual.go +++ b/internal/handler/accrual/accrual.go @@ -11,7 +11,7 @@ import ( "github.com/OlegVankov/fantastic-engine/internal/handler" ) -func SendAccrual(addr string) { +func SendAccrual(addr string, handler *handler.Handler) { client := resty.New() url := addr + "/api/orders/" ball := struct { diff --git a/internal/handler/handler.go b/internal/handler/handler.go index 09b1ef2..73e2d03 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -12,26 +12,19 @@ import ( "github.com/jackc/pgx/v5/pgconn" "github.com/OlegVankov/fantastic-engine/internal/repository" - "github.com/OlegVankov/fantastic-engine/internal/repository/postgres" "github.com/OlegVankov/fantastic-engine/internal/util" ) +type Handler struct { + Repository repository.Repository +} + type credential struct { Login string `json:"login"` Password string `json:"password"` } -var ( - Repository repository.Repository - // = postgres.NewUserRepository("postgresql://postgres:postgres@localhost:5432/gophermart?sslmode=disable") -) - -func SetRepository(dsn string) error { - Repository = postgres.NewUserRepository(dsn) - return nil -} - -func Register(w http.ResponseWriter, r *http.Request) { +func (h *Handler) Register(w http.ResponseWriter, r *http.Request) { c := credential{} @@ -42,7 +35,7 @@ func Register(w http.ResponseWriter, r *http.Request) { return } - user, err := Repository.AddUser(context.Background(), c.Login, c.Password) + user, err := h.Repository.AddUser(context.Background(), c.Login, c.Password) if err != nil { var e *pgconn.PgError if errors.As(err, &e) && e.Code == "23505" { @@ -65,7 +58,7 @@ func Register(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) } -func Login(w http.ResponseWriter, r *http.Request) { +func (h *Handler) Login(w http.ResponseWriter, r *http.Request) { c := credential{} err := json.NewDecoder(r.Body).Decode(&c) @@ -75,7 +68,7 @@ func Login(w http.ResponseWriter, r *http.Request) { return } - user, err := Repository.GetUser(context.Background(), c.Login) + user, err := h.Repository.GetUser(context.Background(), c.Login) if err != nil { if errors.Is(err, pgx.ErrNoRows) { w.WriteHeader(http.StatusUnauthorized) @@ -97,7 +90,7 @@ func Login(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) } -func Orders(w http.ResponseWriter, r *http.Request) { +func (h *Handler) Orders(w http.ResponseWriter, r *http.Request) { body, err := io.ReadAll(r.Body) @@ -119,12 +112,11 @@ func Orders(w http.ResponseWriter, r *http.Request) { username := r.Header.Get("username") - _, err = Repository.AddOrder(context.Background(), username, number) + _, err = h.Repository.AddOrder(context.Background(), username, number) if err != nil { var e *pgconn.PgError if errors.As(err, &e) && e.Code == "23505" { - order, err := Repository.GetOrderByNumber(context.Background(), number) - // fmt.Printf("username: %s number %s %v\n", username, number, order) + order, err := h.Repository.GetOrderByNumber(context.Background(), number) if err == nil { if order.UserLogin == username { w.WriteHeader(http.StatusOK) @@ -141,10 +133,10 @@ func Orders(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusAccepted) } -func GetOrders(w http.ResponseWriter, r *http.Request) { +func (h *Handler) GetOrders(w http.ResponseWriter, r *http.Request) { username := r.Header.Get("username") - orders, err := Repository.GetOrdersByLogin(context.Background(), username) + orders, err := h.Repository.GetOrdersByLogin(context.Background(), username) if err != nil { w.WriteHeader(http.StatusInternalServerError) return @@ -160,7 +152,7 @@ func GetOrders(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(orders) } -func Withdraw(w http.ResponseWriter, r *http.Request) { +func (h *Handler) Withdraw(w http.ResponseWriter, r *http.Request) { username := r.Header.Get("username") withdraw := struct { Order string @@ -177,7 +169,7 @@ func Withdraw(w http.ResponseWriter, r *http.Request) { return } - err = Repository.UpdateWithdraw(context.Background(), username, withdraw.Order, withdraw.Sum) + err = h.Repository.UpdateWithdraw(context.Background(), username, withdraw.Order, withdraw.Sum) if err != nil { if err.Error() == "balance error" { w.WriteHeader(http.StatusPaymentRequired) @@ -191,10 +183,10 @@ func Withdraw(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) } -func Balance(w http.ResponseWriter, r *http.Request) { +func (h *Handler) Balance(w http.ResponseWriter, r *http.Request) { username := r.Header.Get("username") - user, err := Repository.GetBalance(context.Background(), username) + user, err := h.Repository.GetBalance(context.Background(), username) if err != nil { w.WriteHeader(http.StatusInternalServerError) return @@ -213,10 +205,10 @@ func Balance(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(balance) } -func Withdrawals(w http.ResponseWriter, r *http.Request) { +func (h *Handler) Withdrawals(w http.ResponseWriter, r *http.Request) { username := r.Header.Get("username") - wd, err := Repository.GetWithdrawals(r.Context(), username) + wd, err := h.Repository.GetWithdrawals(r.Context(), username) if err != nil { w.WriteHeader(http.StatusInternalServerError) return diff --git a/internal/server.go b/internal/server.go new file mode 100644 index 0000000..26a7939 --- /dev/null +++ b/internal/server.go @@ -0,0 +1,57 @@ +package internal + +import ( + "net/http" + "time" + + "github.com/go-chi/chi/v5" + + "github.com/OlegVankov/fantastic-engine/internal/handler" + "github.com/OlegVankov/fantastic-engine/internal/handler/accrual" + "github.com/OlegVankov/fantastic-engine/internal/repository/postgres" +) + +type Server struct { + srv *http.Server + handler *handler.Handler +} + +func NewServer(addr, dsn string) *Server { + h := &handler.Handler{ + postgres.NewUserRepository(dsn), + } + + router := chi.NewRouter() + + router.Route("/api/user", func(r chi.Router) { + r.Post("/register", h.Register) + r.Post("/login", h.Login) + + r.Route("/", func(r chi.Router) { + r.Use(handler.Auth) + + r.Post("/orders", h.Orders) + r.Get("/orders", h.GetOrders) + + r.Post("/balance/withdraw", h.Withdraw) + r.Get("/balance", h.Balance) + r.Get("/withdrawals", h.Withdrawals) + }) + }) + + return &Server{ + srv: &http.Server{ + Addr: addr, + Handler: router, + MaxHeaderBytes: 1 << 20, + ReadTimeout: 10 * time.Second, + WriteTimeout: 10 * time.Second, + }, + handler: h, + } +} + +func (s *Server) Run(accrualAddr string) { + go accrual.SendAccrual(accrualAddr, s.handler) + s.srv.ListenAndServe() +} From 28dfb3e82666fc78920e9fa212e84fa0a467cd93 Mon Sep 17 00:00:00 2001 From: oleg Date: Wed, 10 Jan 2024 14:18:56 +0300 Subject: [PATCH 08/18] fix struct literal uses unkeyed fields --- internal/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/server.go b/internal/server.go index 26a7939..91167eb 100644 --- a/internal/server.go +++ b/internal/server.go @@ -18,7 +18,7 @@ type Server struct { func NewServer(addr, dsn string) *Server { h := &handler.Handler{ - postgres.NewUserRepository(dsn), + Repository: postgres.NewUserRepository(dsn), } router := chi.NewRouter() From bb4ef5187eac895bfe168c389f86636dcb60ed92 Mon Sep 17 00:00:00 2001 From: oleg Date: Wed, 10 Jan 2024 14:30:15 +0300 Subject: [PATCH 09/18] add gracefully shutdown --- cmd/gophermart/main.go | 2 +- internal/server.go | 26 +++++++++++++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/cmd/gophermart/main.go b/cmd/gophermart/main.go index c69e398..9e020f0 100644 --- a/cmd/gophermart/main.go +++ b/cmd/gophermart/main.go @@ -19,7 +19,7 @@ func main() { flag.StringVar(&serverAddr, "a", "localhost:8080", "адрес и порт запуска сервиса") flag.StringVar(&accrualAddr, "r", "http://localhost:34567", "адрес системы расчёта начислений") - flag.StringVar(&databaseURI, "d", "", "адрес подключения к базе данных") + flag.StringVar(&databaseURI, "d", "postgresql://postgres:postgres@localhost:5432/gophermart?sslmode=disable", "адрес подключения к базе данных") flag.Parse() diff --git a/internal/server.go b/internal/server.go index 91167eb..7bba163 100644 --- a/internal/server.go +++ b/internal/server.go @@ -1,7 +1,13 @@ package internal import ( + "context" + "errors" + "log" "net/http" + "os" + "os/signal" + "syscall" "time" "github.com/go-chi/chi/v5" @@ -53,5 +59,23 @@ func NewServer(addr, dsn string) *Server { func (s *Server) Run(accrualAddr string) { go accrual.SendAccrual(accrualAddr, s.handler) - s.srv.ListenAndServe() + + go func() { + err := s.srv.ListenAndServe() + if !errors.Is(err, http.ErrServerClosed) { + log.Fatal("HTTP server ListenAndServe", err) + } + }() + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) + sig := <-c + + log.Println("server", "Graceful shutdown starter with signal", sig.String()) + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + if err := s.srv.Shutdown(ctx); err != nil { + log.Fatal("server", err) + } + log.Println("server gracefully shutdown complete") } From dc1af1466f341f069b70d5208f6f2f6c568975d9 Mon Sep 17 00:00:00 2001 From: oleg Date: Wed, 10 Jan 2024 17:31:39 +0300 Subject: [PATCH 10/18] add hash password --- internal/handler/handler.go | 10 ++++++++-- internal/util/hash.go | 17 +++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 internal/util/hash.go diff --git a/internal/handler/handler.go b/internal/handler/handler.go index 73e2d03..36f14e9 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -35,7 +35,13 @@ func (h *Handler) Register(w http.ResponseWriter, r *http.Request) { return } - user, err := h.Repository.AddUser(context.Background(), c.Login, c.Password) + pass, err := util.StringToHash(c.Password) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + user, err := h.Repository.AddUser(context.Background(), c.Login, pass) if err != nil { var e *pgconn.PgError if errors.As(err, &e) && e.Code == "23505" { @@ -78,7 +84,7 @@ func (h *Handler) Login(w http.ResponseWriter, r *http.Request) { return } - if user.Password != c.Password { + if !util.CheckPassword(user.Password, c.Password) { w.WriteHeader(http.StatusUnauthorized) return } diff --git a/internal/util/hash.go b/internal/util/hash.go new file mode 100644 index 0000000..825b477 --- /dev/null +++ b/internal/util/hash.go @@ -0,0 +1,17 @@ +package util + +import "golang.org/x/crypto/bcrypt" + +func StringToHash(password string) (string, error) { + bytePassword := []byte(password) + hash, err := bcrypt.GenerateFromPassword(bytePassword, bcrypt.DefaultCost) + if err != nil { + return "", err + } + return string(hash), nil +} + +func CheckPassword(hashPassword, password string) bool { + err := bcrypt.CompareHashAndPassword([]byte(hashPassword), []byte(password)) + return err == nil +} From 25b8c2ab6945a545477797b76f4560bf451ca832 Mon Sep 17 00:00:00 2001 From: oleg Date: Thu, 11 Jan 2024 12:54:55 +0300 Subject: [PATCH 11/18] refactor midleware auth --- internal/handler/checkauth.go | 29 ++++------------------------- internal/util/jwt.go | 12 +++++++++++- 2 files changed, 15 insertions(+), 26 deletions(-) diff --git a/internal/handler/checkauth.go b/internal/handler/checkauth.go index 760f099..12b218f 100644 --- a/internal/handler/checkauth.go +++ b/internal/handler/checkauth.go @@ -1,42 +1,21 @@ package handler import ( - "fmt" "net/http" "strings" - "github.com/golang-jwt/jwt/v4" - "github.com/OlegVankov/fantastic-engine/internal/util" ) func Auth(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - auth := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ") - - if auth == "" { - w.WriteHeader(http.StatusUnauthorized) - return - } - - userClaim := util.UserClaim{} - - token, err := jwt.ParseWithClaims(auth, &userClaim, func(token *jwt.Token) (interface{}, error) { - return []byte("secret_key"), nil - }) - - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - return - } - - if !token.Valid { - fmt.Println("token not valid", userClaim.UserID, userClaim.Username) + token := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ") + username := util.GetUser(token) + if username == "" { w.WriteHeader(http.StatusUnauthorized) return } - - r.Header.Add("username", userClaim.Username) + r.Header.Add("username", username) h.ServeHTTP(w, r) }) } diff --git a/internal/util/jwt.go b/internal/util/jwt.go index b6017f0..7c15b5d 100644 --- a/internal/util/jwt.go +++ b/internal/util/jwt.go @@ -12,6 +12,8 @@ type UserClaim struct { UserID uint64 } +const secretKey = "AsDfGhJkL" + func CreateToken(username string, userid uint64) (string, error) { userClaim := &UserClaim{ Username: username, @@ -22,10 +24,18 @@ func CreateToken(username string, userid uint64) (string, error) { } token := jwt.NewWithClaims(jwt.SigningMethodHS256, userClaim) - tokenString, err := token.SignedString([]byte("secret_key")) + tokenString, err := token.SignedString([]byte(secretKey)) if err != nil { return "", err } return tokenString, nil } + +func GetUser(token string) string { + userClaim := &UserClaim{} + jwt.ParseWithClaims(token, userClaim, func(token *jwt.Token) (interface{}, error) { + return []byte(secretKey), nil + }) + return userClaim.Username +} From bf2ab6ea2c2a7af8b3c7544e2f735b82d24b60a9 Mon Sep 17 00:00:00 2001 From: oleg Date: Thu, 11 Jan 2024 17:14:53 +0300 Subject: [PATCH 12/18] refactor get user from token --- internal/util/jwt.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/internal/util/jwt.go b/internal/util/jwt.go index 7c15b5d..ad9e8e2 100644 --- a/internal/util/jwt.go +++ b/internal/util/jwt.go @@ -32,10 +32,16 @@ func CreateToken(username string, userid uint64) (string, error) { return tokenString, nil } -func GetUser(token string) string { +func GetUser(tokenString string) string { userClaim := &UserClaim{} - jwt.ParseWithClaims(token, userClaim, func(token *jwt.Token) (interface{}, error) { + token, err := jwt.ParseWithClaims(tokenString, userClaim, func(token *jwt.Token) (interface{}, error) { return []byte(secretKey), nil }) + if err != nil { + return "" + } + if !token.Valid { + return "" + } return userClaim.Username } From ffa39bd22a77316befdafc7a76490321d25ac1cb Mon Sep 17 00:00:00 2001 From: oleg Date: Thu, 11 Jan 2024 17:28:45 +0300 Subject: [PATCH 13/18] refactor add check header token --- internal/util/jwt.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/util/jwt.go b/internal/util/jwt.go index ad9e8e2..35dffb3 100644 --- a/internal/util/jwt.go +++ b/internal/util/jwt.go @@ -1,6 +1,7 @@ package util import ( + "fmt" "time" "github.com/golang-jwt/jwt/v4" @@ -35,6 +36,9 @@ func CreateToken(username string, userid uint64) (string, error) { func GetUser(tokenString string) string { userClaim := &UserClaim{} token, err := jwt.ParseWithClaims(tokenString, userClaim, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) + } return []byte(secretKey), nil }) if err != nil { From 56800debe1f6b962773e8735a4ea31ae1aa00bd7 Mon Sep 17 00:00:00 2001 From: oleg Date: Thu, 11 Jan 2024 17:44:46 +0300 Subject: [PATCH 14/18] add working with context --- internal/handler/accrual/accrual.go | 5 +++-- internal/handler/handler.go | 15 +++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/internal/handler/accrual/accrual.go b/internal/handler/accrual/accrual.go index e43a25f..8828441 100644 --- a/internal/handler/accrual/accrual.go +++ b/internal/handler/accrual/accrual.go @@ -12,6 +12,7 @@ import ( ) func SendAccrual(addr string, handler *handler.Handler) { + ctx := context.Background() client := resty.New() url := addr + "/api/orders/" ball := struct { @@ -21,7 +22,7 @@ func SendAccrual(addr string, handler *handler.Handler) { }{} for { - orders, err := handler.Repository.GetOrders(context.Background()) + orders, err := handler.Repository.GetOrders(ctx) if err != nil { continue } @@ -37,7 +38,7 @@ func SendAccrual(addr string, handler *handler.Handler) { if resp.StatusCode() == http.StatusOK && ball.Status == "PROCESSED" { - err := handler.Repository.UpdateOrder(context.Background(), ball.Order, ball.Status, ball.Accrual) + err := handler.Repository.UpdateOrder(ctx, ball.Order, ball.Status, ball.Accrual) if err != nil { fmt.Printf("[ERROR] %s\n", err.Error()) continue diff --git a/internal/handler/handler.go b/internal/handler/handler.go index 36f14e9..342686c 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -1,7 +1,6 @@ package handler import ( - "context" "encoding/json" "errors" "fmt" @@ -41,7 +40,7 @@ func (h *Handler) Register(w http.ResponseWriter, r *http.Request) { return } - user, err := h.Repository.AddUser(context.Background(), c.Login, pass) + user, err := h.Repository.AddUser(r.Context(), c.Login, pass) if err != nil { var e *pgconn.PgError if errors.As(err, &e) && e.Code == "23505" { @@ -74,7 +73,7 @@ func (h *Handler) Login(w http.ResponseWriter, r *http.Request) { return } - user, err := h.Repository.GetUser(context.Background(), c.Login) + user, err := h.Repository.GetUser(r.Context(), c.Login) if err != nil { if errors.Is(err, pgx.ErrNoRows) { w.WriteHeader(http.StatusUnauthorized) @@ -118,11 +117,11 @@ func (h *Handler) Orders(w http.ResponseWriter, r *http.Request) { username := r.Header.Get("username") - _, err = h.Repository.AddOrder(context.Background(), username, number) + _, err = h.Repository.AddOrder(r.Context(), username, number) if err != nil { var e *pgconn.PgError if errors.As(err, &e) && e.Code == "23505" { - order, err := h.Repository.GetOrderByNumber(context.Background(), number) + order, err := h.Repository.GetOrderByNumber(r.Context(), number) if err == nil { if order.UserLogin == username { w.WriteHeader(http.StatusOK) @@ -142,7 +141,7 @@ func (h *Handler) Orders(w http.ResponseWriter, r *http.Request) { func (h *Handler) GetOrders(w http.ResponseWriter, r *http.Request) { username := r.Header.Get("username") - orders, err := h.Repository.GetOrdersByLogin(context.Background(), username) + orders, err := h.Repository.GetOrdersByLogin(r.Context(), username) if err != nil { w.WriteHeader(http.StatusInternalServerError) return @@ -175,7 +174,7 @@ func (h *Handler) Withdraw(w http.ResponseWriter, r *http.Request) { return } - err = h.Repository.UpdateWithdraw(context.Background(), username, withdraw.Order, withdraw.Sum) + err = h.Repository.UpdateWithdraw(r.Context(), username, withdraw.Order, withdraw.Sum) if err != nil { if err.Error() == "balance error" { w.WriteHeader(http.StatusPaymentRequired) @@ -192,7 +191,7 @@ func (h *Handler) Withdraw(w http.ResponseWriter, r *http.Request) { func (h *Handler) Balance(w http.ResponseWriter, r *http.Request) { username := r.Header.Get("username") - user, err := h.Repository.GetBalance(context.Background(), username) + user, err := h.Repository.GetBalance(r.Context(), username) if err != nil { w.WriteHeader(http.StatusInternalServerError) return From 7f33cc467a51b685d87077aeafcaf1e9b7d31c90 Mon Sep 17 00:00:00 2001 From: oleg Date: Fri, 19 Jan 2024 18:41:19 +0300 Subject: [PATCH 15/18] refactor get orders --- internal/handler/accrual/accrual.go | 3 ++- internal/repository/postgres/user.go | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/handler/accrual/accrual.go b/internal/handler/accrual/accrual.go index 8828441..85a9cff 100644 --- a/internal/handler/accrual/accrual.go +++ b/internal/handler/accrual/accrual.go @@ -23,6 +23,7 @@ func SendAccrual(addr string, handler *handler.Handler) { for { orders, err := handler.Repository.GetOrders(ctx) + if err != nil { continue } @@ -36,7 +37,7 @@ func SendAccrual(addr string, handler *handler.Handler) { fmt.Printf("[ERROR] %s\n", err.Error()) } - if resp.StatusCode() == http.StatusOK && ball.Status == "PROCESSED" { + if resp.StatusCode() == http.StatusOK { err := handler.Repository.UpdateOrder(ctx, ball.Order, ball.Status, ball.Accrual) if err != nil { diff --git a/internal/repository/postgres/user.go b/internal/repository/postgres/user.go index eb8eb47..9045c41 100644 --- a/internal/repository/postgres/user.go +++ b/internal/repository/postgres/user.go @@ -213,7 +213,7 @@ func (r *UserRepository) GetBalance(ctx context.Context, username string) (*mode func (r *UserRepository) GetOrders(ctx context.Context) ([]model.Order, error) { orders := []model.Order{} - err := r.db.SelectContext(ctx, &orders, "select * from orders") + err := r.db.SelectContext(ctx, &orders, "select * from orders where status = 'NEW'") if err != nil { return nil, err } From 60838f1baff354245d7d89f97c6a69c409f39fe7 Mon Sep 17 00:00:00 2001 From: oleg Date: Fri, 19 Jan 2024 19:00:57 +0300 Subject: [PATCH 16/18] fix potential memory leak --- internal/handler/accrual/accrual.go | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/internal/handler/accrual/accrual.go b/internal/handler/accrual/accrual.go index 85a9cff..7d9b33c 100644 --- a/internal/handler/accrual/accrual.go +++ b/internal/handler/accrual/accrual.go @@ -11,6 +11,19 @@ import ( "github.com/OlegVankov/fantastic-engine/internal/handler" ) +func sleep(ctx context.Context, interval time.Duration) error { + timer := time.NewTimer(interval) + select { + case <-ctx.Done(): + if !timer.Stop() { + <-timer.C + } + return ctx.Err() + case <-timer.C: + return nil + } +} + func SendAccrual(addr string, handler *handler.Handler) { ctx := context.Background() client := resty.New() @@ -49,6 +62,8 @@ func SendAccrual(addr string, handler *handler.Handler) { } - <-time.After(time.Second * time.Duration(5)) + if err := sleep(ctx, time.Duration(5)*time.Second); err != nil { + fmt.Printf("[ERROR] %s\n", err.Error()) + } } } From 2f5f087b989b6516f5f6bf5606432301075a07f8 Mon Sep 17 00:00:00 2001 From: oleg Date: Tue, 23 Jan 2024 19:03:58 +0300 Subject: [PATCH 17/18] refactor accrual, handler and package util --- internal/handler/accrual/accrual.go | 22 +++++----------------- internal/handler/checkauth.go | 4 ++-- internal/handler/handler.go | 21 ++++++++++++++------- internal/util/{ => hash}/hash.go | 2 +- internal/util/{ => jwt}/jwt.go | 2 +- internal/util/{ => lun}/lun.go | 2 +- 6 files changed, 24 insertions(+), 29 deletions(-) rename internal/util/{ => hash}/hash.go (97%) rename internal/util/{ => jwt}/jwt.go (98%) rename internal/util/{ => lun}/lun.go (95%) diff --git a/internal/handler/accrual/accrual.go b/internal/handler/accrual/accrual.go index 7d9b33c..4ea8c4c 100644 --- a/internal/handler/accrual/accrual.go +++ b/internal/handler/accrual/accrual.go @@ -11,19 +11,6 @@ import ( "github.com/OlegVankov/fantastic-engine/internal/handler" ) -func sleep(ctx context.Context, interval time.Duration) error { - timer := time.NewTimer(interval) - select { - case <-ctx.Done(): - if !timer.Stop() { - <-timer.C - } - return ctx.Err() - case <-timer.C: - return nil - } -} - func SendAccrual(addr string, handler *handler.Handler) { ctx := context.Background() client := resty.New() @@ -33,7 +20,11 @@ func SendAccrual(addr string, handler *handler.Handler) { Status string `json:"status"` Accrual float64 `json:"accrual"` }{} - for { + + timer := time.NewTimer(time.Duration(5) * time.Second) + defer timer.Stop() + + for _ = range timer.C { orders, err := handler.Repository.GetOrders(ctx) @@ -62,8 +53,5 @@ func SendAccrual(addr string, handler *handler.Handler) { } - if err := sleep(ctx, time.Duration(5)*time.Second); err != nil { - fmt.Printf("[ERROR] %s\n", err.Error()) - } } } diff --git a/internal/handler/checkauth.go b/internal/handler/checkauth.go index 12b218f..9a09224 100644 --- a/internal/handler/checkauth.go +++ b/internal/handler/checkauth.go @@ -4,13 +4,13 @@ import ( "net/http" "strings" - "github.com/OlegVankov/fantastic-engine/internal/util" + "github.com/OlegVankov/fantastic-engine/internal/util/jwt" ) func Auth(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { token := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ") - username := util.GetUser(token) + username := jwt.GetUser(token) if username == "" { w.WriteHeader(http.StatusUnauthorized) return diff --git a/internal/handler/handler.go b/internal/handler/handler.go index 342686c..d54b1af 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -11,7 +11,9 @@ import ( "github.com/jackc/pgx/v5/pgconn" "github.com/OlegVankov/fantastic-engine/internal/repository" - "github.com/OlegVankov/fantastic-engine/internal/util" + "github.com/OlegVankov/fantastic-engine/internal/util/hash" + "github.com/OlegVankov/fantastic-engine/internal/util/jwt" + "github.com/OlegVankov/fantastic-engine/internal/util/lun" ) type Handler struct { @@ -34,7 +36,7 @@ func (h *Handler) Register(w http.ResponseWriter, r *http.Request) { return } - pass, err := util.StringToHash(c.Password) + pass, err := hash.StringToHash(c.Password) if err != nil { w.WriteHeader(http.StatusInternalServerError) return @@ -52,7 +54,7 @@ func (h *Handler) Register(w http.ResponseWriter, r *http.Request) { return } - tkn, err := util.CreateToken(user.Login, user.ID) + tkn, err := jwt.CreateToken(user.Login, user.ID) if err != nil { w.WriteHeader(http.StatusInternalServerError) return @@ -83,12 +85,17 @@ func (h *Handler) Login(w http.ResponseWriter, r *http.Request) { return } - if !util.CheckPassword(user.Password, c.Password) { + if !hash.CheckPassword(user.Password, c.Password) { w.WriteHeader(http.StatusUnauthorized) return } - tkn, _ := util.CreateToken(user.Login, user.ID) + tkn, err := jwt.CreateToken(user.Login, user.ID) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + authorization := fmt.Sprintf("Bearer %s", tkn) w.Header().Add("Authorization", authorization) @@ -110,7 +117,7 @@ func (h *Handler) Orders(w http.ResponseWriter, r *http.Request) { number := string(body) - if !util.CheckLun(number) { + if !lun.CheckLun(number) { w.WriteHeader(http.StatusUnprocessableEntity) return } @@ -169,7 +176,7 @@ func (h *Handler) Withdraw(w http.ResponseWriter, r *http.Request) { return } - if !util.CheckLun(withdraw.Order) { + if !lun.CheckLun(withdraw.Order) { w.WriteHeader(http.StatusUnprocessableEntity) return } diff --git a/internal/util/hash.go b/internal/util/hash/hash.go similarity index 97% rename from internal/util/hash.go rename to internal/util/hash/hash.go index 825b477..8f3b28f 100644 --- a/internal/util/hash.go +++ b/internal/util/hash/hash.go @@ -1,4 +1,4 @@ -package util +package hash import "golang.org/x/crypto/bcrypt" diff --git a/internal/util/jwt.go b/internal/util/jwt/jwt.go similarity index 98% rename from internal/util/jwt.go rename to internal/util/jwt/jwt.go index 35dffb3..977d4e6 100644 --- a/internal/util/jwt.go +++ b/internal/util/jwt/jwt.go @@ -1,4 +1,4 @@ -package util +package jwt import ( "fmt" diff --git a/internal/util/lun.go b/internal/util/lun/lun.go similarity index 95% rename from internal/util/lun.go rename to internal/util/lun/lun.go index a7e2a7b..38c877e 100644 --- a/internal/util/lun.go +++ b/internal/util/lun/lun.go @@ -1,4 +1,4 @@ -package util +package lun import "strconv" From 14b45d05a71ca3600a83707a490983b54cc7b2e5 Mon Sep 17 00:00:00 2001 From: oleg Date: Tue, 23 Jan 2024 19:08:06 +0300 Subject: [PATCH 18/18] fix loop in accrual --- internal/handler/accrual/accrual.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/handler/accrual/accrual.go b/internal/handler/accrual/accrual.go index 4ea8c4c..0198b41 100644 --- a/internal/handler/accrual/accrual.go +++ b/internal/handler/accrual/accrual.go @@ -24,7 +24,7 @@ func SendAccrual(addr string, handler *handler.Handler) { timer := time.NewTimer(time.Duration(5) * time.Second) defer timer.Stop() - for _ = range timer.C { + for range timer.C { orders, err := handler.Repository.GetOrders(ctx)