From 3009301b67b54e24325614e39d71e11216813490 Mon Sep 17 00:00:00 2001 From: Demosfenys Date: Wed, 1 Feb 2023 13:17:44 +0300 Subject: [PATCH 1/6] =?UTF-8?q?=D0=9D=D0=B0=D0=B1=D1=80=D0=BE=D1=81=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=BE=D0=B1=D1=80=D0=B0=D1=89=D0=B5=D0=BD=D0=B8=D0=B9?= =?UTF-8?q?=20=D0=BA=20=D0=91=D0=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/authentication/Crypt.go | 31 ++++ cmd/authentication/file1.go | 33 ++++ cmd/errorsGM/errorGopherMart.go | 33 ++++ cmd/gophermart/Database.go | 84 ----------- cmd/gophermart/main.go | 10 +- cmd/router/Database.go | 215 +++++++++++++++++++++++++++ cmd/router/accrualEvent.go | 66 ++++++++ cmd/router/gzip.go | 36 +++++ cmd/{gophermart => router}/router.go | 7 +- go.mod | 2 + 10 files changed, 427 insertions(+), 90 deletions(-) create mode 100644 cmd/authentication/Crypt.go create mode 100644 cmd/authentication/file1.go create mode 100644 cmd/errorsGM/errorGopherMart.go delete mode 100644 cmd/gophermart/Database.go create mode 100644 cmd/router/Database.go create mode 100644 cmd/router/accrualEvent.go create mode 100644 cmd/router/gzip.go rename cmd/{gophermart => router}/router.go (95%) diff --git a/cmd/authentication/Crypt.go b/cmd/authentication/Crypt.go new file mode 100644 index 000000000..61a523a53 --- /dev/null +++ b/cmd/authentication/Crypt.go @@ -0,0 +1,31 @@ +package authentication + +import ( + "crypto/aes" + "log" + "time" +) + +var key = append([]byte(time.Now().Format("02-01-2006 15")), []byte(":00")...) + +func CryptoToken(token []byte) ([]byte, error) { + aesBlock, err := aes.NewCipher(key) + if err != nil { + log.Fatal(err) + return nil, err + } + cryptoString := make([]byte, len(token)) + aesBlock.Encrypt(cryptoString, token) + return cryptoString, nil +} + +func DeCryptoToken(token []byte) ([]byte, error) { + aesBlock, err := aes.NewCipher(key) + if err != nil { + log.Fatal(err) + return nil, err + } + deCryptoString := make([]byte, len(token)) + aesBlock.Decrypt(deCryptoString, token) + return deCryptoString, nil +} diff --git a/cmd/authentication/file1.go b/cmd/authentication/file1.go new file mode 100644 index 000000000..cda0a5e9a --- /dev/null +++ b/cmd/authentication/file1.go @@ -0,0 +1,33 @@ +package authentication + +import ( + "crypto/md5" + "encoding/hex" +) + +type Name struct { + Login string `json:"-"` + Client string `json:"-"` + CurrentPoints int `json:"current"` + WithdrawnPoints int `json:"withdrawn"` + Coockie string `json:"-"` +} + +func New() *Name { + return &Name{} +} + +func func1(login string, pass string) (user Name, err error) { + user.Login = login + logPas := []byte(login + pass) + h := md5.New() + h.Write(logPas) + userByte := h.Sum(nil) + user.Client = hex.EncodeToString(userByte) // возвращать для хранения в таблице как пользователя + userByteCrypt, err := CryptoToken(userByte) + if err != nil { + return Name{}, err + } + user.Coockie = hex.EncodeToString(userByteCrypt) // возвращать для передачи клиенту (временная кука) + return user, nil +} diff --git a/cmd/errorsGM/errorGopherMart.go b/cmd/errorsGM/errorGopherMart.go new file mode 100644 index 000000000..e195f7fcc --- /dev/null +++ b/cmd/errorsGM/errorGopherMart.go @@ -0,0 +1,33 @@ +package errorsGM + +type ErrorGopherMart struct { + Code string + Err error +} + +func (err ErrorGopherMart) Error() string { + return err.Err.Error() +} + +const ( + //http + GetError = "H01" + + //Server status + StatusOk = "S01" + StatusTooManyRequests = "S02" + StatusInternalServerError = "S03" + + //Marshal + MarshalError = "M01" + UnmarshalError = "M02" + + //io + ReadAllError = "I01" + + //strconv + ParseIntError = "C01" + + //WTF?? + RespStatusCodeNotMatch = "WTF" +) diff --git a/cmd/gophermart/Database.go b/cmd/gophermart/Database.go deleted file mode 100644 index 9cd457add..000000000 --- a/cmd/gophermart/Database.go +++ /dev/null @@ -1,84 +0,0 @@ -package main - -import ( - "context" - "database/sql" - "time" -) - -var СreateTableOperations = `CREATE TABLE OperationsGopherMart( -users varchar(64), -order_number varchar(32), -uploaded_at varchar(32), -status varchar(32), -rewards_points varchar(32), -)` - -var СreateTableUsers = `CREATE TABLE UsersGopherMart( -login varchar(32), -users varchar(64), -balance_points varchar(32), -withdrawn_points varchar(32), -)` - -type DBI interface { - Connect(connStr string) (err error) - CreateTable() error - Ping(ctx context.Context) error - Close() error -} - -type Database struct { - connection *sql.DB -} - -func InitDB() (*Database, error) { - return &Database{}, nil -} - -func (db *Database) Connect(connStr string) (err error) { - db.connection, err = sql.Open("pgx", connStr) - if err != nil { - return err - } - if err = db.CreateTable(); err != nil { - return err - } - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) - defer cancel() - if err = db.Ping(ctx); err != nil { - return err - } - return nil -} - -func (db *Database) CreateTable() error { - if _, err := db.connection.Exec("Drop TABLE OperationsGopherMart"); err != nil { - return err - } - if _, err := db.connection.Exec("Drop TABLE UsersGopherMart"); err != nil { - return err - } - if _, err := db.connection.Exec(СreateTableOperations); err != nil { - return err - } - if _, err := db.connection.Exec("CREATE UNIQUE INDEX order_index ON OperationsGopherMart (order_number)"); err != nil { - return err - } - if _, err := db.connection.Exec(СreateTableUsers); err != nil { - return err - } - _, err := db.connection.Exec("CREATE UNIQUE INDEX login_index ON UsersGopherMart (login,users)") - return err -} - -func (db *Database) Ping(ctx context.Context) error { - if err := db.connection.PingContext(ctx); err != nil { - return err - } - return nil -} - -func (db *Database) Close() error { - return db.connection.Close() -} diff --git a/cmd/gophermart/main.go b/cmd/gophermart/main.go index d31ba2348..f156021b4 100644 --- a/cmd/gophermart/main.go +++ b/cmd/gophermart/main.go @@ -1,10 +1,14 @@ package main -import "log" +import ( + "log" + + "GopherMart/cmd/router" +) func main() { - rout := InitServer() - err := rout.router() + rout := router.InitServer() + err := rout.Router() if err != nil { log.Fatal("Router:", err) } diff --git a/cmd/router/Database.go b/cmd/router/Database.go new file mode 100644 index 000000000..506b5c4b6 --- /dev/null +++ b/cmd/router/Database.go @@ -0,0 +1,215 @@ +package router + +import ( + "context" + "database/sql" + "time" +) + +var CreateTableOperations = `CREATE TABLE OperationsGopherMart( +order_number varchar(32), +users varchar(64), +uploaded_at integer, +Status varchar(32), +operation varchar(32), +points integer, +)` + +var CreateTableUsers = `CREATE TABLE UsersGopherMart( +login varchar(32), +users varchar(64), +current_points integer, +withdrawn_points integer, +)` + +type Operation struct { + Order_number string `json:"number"` + Status string `json:"Status"` + Points uint `json:"accrual"` + Uploaded_at *time.Time `json:"uploaded_at"` + Users string `json:"-"` + //Operation string `json:"-"` // "accrual" or "withdraw" +} + +const ( + accrual = "accrual" + withdraw = "withdraw" + + processing = "PROCESSING" + registered = "REGISTERED" + neworder = "NEW" + invalid = "INVALID" +) + +type DBI interface { + Connect(connStr string) (err error) + CreateTable() error + Ping(ctx context.Context) error + Close() error +} + +type Database struct { + connection *sql.DB +} + +func InitDB() (*Database, error) { + return &Database{}, nil +} + +func (db *Database) Connect(connStr string) (err error) { + db.connection, err = sql.Open("pgx", connStr) + if err != nil { + return err + } + if err = db.CreateTable(); err != nil { + return err + } + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + if err = db.Ping(ctx); err != nil { + return err + } + return nil +} + +func (db *Database) CreateTable() error { + if _, err := db.connection.Exec("Drop TABLE OperationsGopherMart"); err != nil { + return err + } + if _, err := db.connection.Exec("Drop TABLE UsersGopherMart"); err != nil { + return err + } + if _, err := db.connection.Exec(CreateTableOperations); err != nil { + return err + } + if _, err := db.connection.Exec("CREATE UNIQUE INDEX order_index ON OperationsGopherMart (order_number)"); err != nil { + return err + } + if _, err := db.connection.Exec(CreateTableUsers); err != nil { + return err + } + _, err := db.connection.Exec("CREATE UNIQUE INDEX login_index ON UsersGopherMart (login,users)") + return err +} + +func (db *Database) Ping(ctx context.Context) error { + if err := db.connection.PingContext(ctx); err != nil { + return err + } + return nil +} + +func (db *Database) Close() error { + return db.connection.Close() +} + +// отловить ошибку с уникальным номером заказа, там где будет вызываться +func (db *Database) WriteOrderAccrual(order string, user string) (err error) { + _, err = db.connection.Exec("insert into OperationsGopherMart (order_number, users, operation) values ($1,$2,$3)", order, user, accrual) + if err != nil { + return err + } + return nil +} + +func (db *Database) ReadOrderAccrual(storage string, user string, order string) (op Operation, err error) { + row := db.connection.QueryRow("select order_number, status, uploaded_at, points from OperationsGopherMart where order_number = $1 and users = $2 and operation != $3", + order, user, accrual) + err = row.Scan(&op.Order_number, &op.Status, &op.Uploaded_at, &op.Points) + if err != nil { + return Operation{}, err + } + if (op.Status == neworder) || (op.Status == processing) { + op.Status, op.Points, err = db.UpdateOrderAccrual(storage, op.Order_number, user) + if err != nil { + return Operation{}, err + } + } + return op, nil +} + +// проверить ошибки, return +func (db *Database) ReadAllOrderAccrualUser(storage string, user string) (ops []Operation, err error) { + var op Operation + rows, err := db.connection.Query("select order_number, status, uploaded_at, points from OperationsGopherMart where users = $1 and operation != $2", user, accrual) + defer rows.Close() + for rows.Next() { + err := rows.Scan(&op.Order_number, &op.Status, &op.Uploaded_at, &op.Points) + if err != nil { + return nil, err + } + if (op.Status == neworder) || (op.Status == processing) { + op.Status, op.Points, err = db.UpdateOrderAccrual(storage, op.Order_number, user) + if err != nil { + return nil, err + } + } + ops = append(ops, op) + } + return ops, nil +} + +func (db *Database) UpdateOrderAccrual(storage string, order string, user string) (status string, points uint, err error) { + orderStatus, err := accrualOrderStatus(storage, order) + if err != nil { + return "", 0, err + } + _, err = db.connection.Exec("UPDATE OperationsGopherMart SET status = $1,point = $2 WHERE order=$3", orderStatus.Status, orderStatus.Point, order) + if err != nil { + return "", 0, err + } + //зачислить балы пользователю + if status == registered { + _, err = db.connection.Exec("UPDATE UsersGopherMart SET current_points = current_points + $1 WHERE users=$2", orderStatus.Point, user) + if err != nil { + return "", 0, err + } + } + return orderStatus.Status, orderStatus.Point, nil +} + +type UserPoints struct { + CurrentPoints int `json:"current"` + WithdrawnPoints int `json:"withdrawn"` +} + +// информация по счёту, но перед этим ReadAllOrderAccrualUser +func (db *Database) ReadUserPoints(storage string, user string) (u UserPoints, err error) { + if _, err = db.ReadAllOrderAccrualUser(storage, user); err != nil { + return UserPoints{}, err + } + row := db.connection.QueryRow("select current_points, withdrawn_points from UsersGopherMart where users = $1", + user) + if err = row.Scan(&u.CurrentPoints, &u.WithdrawnPoints); err != nil { + return UserPoints{}, err + } + return u, nil +} + +// списание +// попытка списать баллы, но перед этим ReadAllOrderAccrualUser +func (db *Database) WithdrawnUserPoints(storage string, user string, sum int) (err error) { + var u UserPoints + // + if _, err = db.ReadAllOrderAccrualUser(storage, user); err != nil { + return err + } + u, err = db.ReadUserPoints(storage, user) + if err != nil { + return err + } + if u.CurrentPoints >= sum { + // списываем и ретурн + _, err = db.connection.Exec("UPDATE UsersGopherMart SET current_points = current_points - $1 and withdrawn_points = withdrawn_points + $1 WHERE users=$2", + sum, user) + if err != nil { + return err + } + + } + + // ошибка, недостаточно баллов и ретурн + return nil +} + +// создание заказа со списанием diff --git a/cmd/router/accrualEvent.go b/cmd/router/accrualEvent.go new file mode 100644 index 000000000..71fff78eb --- /dev/null +++ b/cmd/router/accrualEvent.go @@ -0,0 +1,66 @@ +package router + +import ( + "encoding/json" + "io" + "net/http" + "strconv" + "time" + + "GopherMart/cmd/errorsGM" +) + +type requestAccrual struct { + Number string `json:"number"` + Status string `json:"Status"` + Point uint `json:"accrual"` + UploadedAt *time.Time `json:"uploaded_at"` +} + +func accrualOrderStatus(storageAccrual string, order string) (bodyRequest requestAccrual, err error) { + var errGet *errorsGM.ErrorGopherMart + accrual, sec, errGet := accrualGet(storageAccrual, order) + for ; sec != 0; accrual, sec, err = accrualGet(storageAccrual, order) { + time.Sleep(time.Duration(sec) * time.Second) + } + if errGet != nil { + return requestAccrual{}, errGet + } + + return accrual, nil +} + +func accrualGet(storage string, order string) (bodyRequest requestAccrual, duration int64, errGM *errorsGM.ErrorGopherMart) { + get := storage + "/api/orders/" + order + resp, err := http.Get(get) + if err != nil { + return requestAccrual{}, 0, &errorsGM.ErrorGopherMart{errorsGM.UnmarshalError, err} + } + switch resp.StatusCode { + + case 200: + body, err := io.ReadAll(resp.Request.Body) + if err != nil { + return requestAccrual{}, 0, &errorsGM.ErrorGopherMart{errorsGM.ReadAllError, err} + } + err = json.Unmarshal(body, &bodyRequest) + if err != nil { + return requestAccrual{}, 0, &errorsGM.ErrorGopherMart{errorsGM.UnmarshalError, err} + } + return bodyRequest, 0, nil + + case 429: + header := resp.Header + a := header["Retry-After"][0] + sec, err := strconv.ParseInt(a, 10, 0) + if err != nil { + return requestAccrual{}, 0, &errorsGM.ErrorGopherMart{errorsGM.StatusTooManyRequests, err} + } + return requestAccrual{}, sec, &errorsGM.ErrorGopherMart{errorsGM.StatusTooManyRequests, err} // + + case 500: + return requestAccrual{}, 0, &errorsGM.ErrorGopherMart{errorsGM.StatusInternalServerError, err} // + } + + return requestAccrual{}, 0, &errorsGM.ErrorGopherMart{errorsGM.RespStatusCodeNotMatch, err} +} diff --git a/cmd/router/gzip.go b/cmd/router/gzip.go new file mode 100644 index 000000000..ced1246b7 --- /dev/null +++ b/cmd/router/gzip.go @@ -0,0 +1,36 @@ +package router + +import ( + "bytes" + "compress/gzip" + "fmt" + "io" + + "github.com/labstack/echo" +) + +func (s serverMart) gzip(next echo.HandlerFunc) echo.HandlerFunc { + fmt.Println("=> gzip run") + + return func(c echo.Context) error { + if c.Request().Header.Get("Content-Encoding") != "gzip" { + return next(c) + } + + qzBody, err := gzip.NewReader(c.Request().Body) + if err != nil { + return fmt.Errorf("qz is not exist") + } + + body, err := io.ReadAll(qzBody) + if err != nil { + c.Error(echo.ErrInternalServerError) + return fmt.Errorf("URL does not exist") + } + stringReader := bytes.NewReader(body) + + c.Request().Body = io.NopCloser(stringReader) + + return next(c) + } +} diff --git a/cmd/gophermart/router.go b/cmd/router/router.go similarity index 95% rename from cmd/gophermart/router.go rename to cmd/router/router.go index 9fe86e804..b366eb2dd 100644 --- a/cmd/gophermart/router.go +++ b/cmd/router/router.go @@ -1,4 +1,4 @@ -package main +package router import ( "flag" @@ -23,7 +23,7 @@ func InitServer() *serverMart { return &serverMart{} } -func (s serverMart) router() error { +func (s serverMart) Router() error { if err := s.parseFlagCfg(); err != nil { return err } @@ -33,10 +33,11 @@ func (s serverMart) router() error { e := echo.New() + e.Use(s.gzip) + // //e.POST("/api/user/register", s.postAPIUserRegister) //e.POST("/api/user/login", s.postAPIUserLogin) // - //e.Use(s.gzip) //e.Use(s.CheakCookies) // //e.GET("/api/user/orders", s.getAPIUserOrders) diff --git a/go.mod b/go.mod index 8e5893236..0c6a40cf5 100644 --- a/go.mod +++ b/go.mod @@ -4,10 +4,12 @@ go 1.19 require ( github.com/caarlos0/env v3.5.0+incompatible // indirect + github.com/jackc/pgx v3.6.2+incompatible // indirect github.com/labstack/echo v3.3.10+incompatible // indirect github.com/labstack/gommon v0.4.0 // indirect github.com/mattn/go-colorable v0.1.11 // indirect github.com/mattn/go-isatty v0.0.14 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.1 // indirect golang.org/x/crypto v0.5.0 // indirect From 11260f28f650a095e4eac202b1fd29e3feceb0bc Mon Sep 17 00:00:00 2001 From: Demosfenys Date: Thu, 2 Feb 2023 19:17:43 +0300 Subject: [PATCH 2/6] =?UTF-8?q?=D0=9D=D0=B0=D0=B1=D1=80=D0=BE=D1=81=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=BE=D0=B1=D1=80=D0=B0=D1=89=D0=B5=D0=BD=D0=B8=D0=B9?= =?UTF-8?q?=20=D0=BA=20=D0=91=D0=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/authentication/file1.go | 33 ------ cmd/{authentication => events}/Crypt.go | 2 +- cmd/{router => events}/Database.go | 149 +++++++++++++++++++----- cmd/{router => events}/accrualEvent.go | 26 +++-- cmd/router/mwCheakCookies.go | 17 +++ cmd/router/postAPIUserLogin.go | 35 ++++++ cmd/router/postAPIUserRegister.go | 54 +++++++++ cmd/router/router.go | 22 ++-- go.mod | 4 + 9 files changed, 262 insertions(+), 80 deletions(-) delete mode 100644 cmd/authentication/file1.go rename cmd/{authentication => events}/Crypt.go (96%) rename cmd/{router => events}/Database.go (55%) rename cmd/{router => events}/accrualEvent.go (71%) create mode 100644 cmd/router/mwCheakCookies.go create mode 100644 cmd/router/postAPIUserLogin.go create mode 100644 cmd/router/postAPIUserRegister.go diff --git a/cmd/authentication/file1.go b/cmd/authentication/file1.go deleted file mode 100644 index cda0a5e9a..000000000 --- a/cmd/authentication/file1.go +++ /dev/null @@ -1,33 +0,0 @@ -package authentication - -import ( - "crypto/md5" - "encoding/hex" -) - -type Name struct { - Login string `json:"-"` - Client string `json:"-"` - CurrentPoints int `json:"current"` - WithdrawnPoints int `json:"withdrawn"` - Coockie string `json:"-"` -} - -func New() *Name { - return &Name{} -} - -func func1(login string, pass string) (user Name, err error) { - user.Login = login - logPas := []byte(login + pass) - h := md5.New() - h.Write(logPas) - userByte := h.Sum(nil) - user.Client = hex.EncodeToString(userByte) // возвращать для хранения в таблице как пользователя - userByteCrypt, err := CryptoToken(userByte) - if err != nil { - return Name{}, err - } - user.Coockie = hex.EncodeToString(userByteCrypt) // возвращать для передачи клиенту (временная кука) - return user, nil -} diff --git a/cmd/authentication/Crypt.go b/cmd/events/Crypt.go similarity index 96% rename from cmd/authentication/Crypt.go rename to cmd/events/Crypt.go index 61a523a53..eaea7208b 100644 --- a/cmd/authentication/Crypt.go +++ b/cmd/events/Crypt.go @@ -1,4 +1,4 @@ -package authentication +package events import ( "crypto/aes" diff --git a/cmd/router/Database.go b/cmd/events/Database.go similarity index 55% rename from cmd/router/Database.go rename to cmd/events/Database.go index 506b5c4b6..6abce5594 100644 --- a/cmd/router/Database.go +++ b/cmd/events/Database.go @@ -1,34 +1,37 @@ -package router +package events import ( "context" + "crypto/md5" "database/sql" + "encoding/hex" "time" + + "github.com/pkg/errors" ) var CreateTableOperations = `CREATE TABLE OperationsGopherMart( order_number varchar(32), -users varchar(64), -uploaded_at integer, -Status varchar(32), +login varchar(64), +uploaded_at varchar(32), +status varchar(32), operation varchar(32), points integer, )` var CreateTableUsers = `CREATE TABLE UsersGopherMart( login varchar(32), -users varchar(64), +password varchar(64), current_points integer, withdrawn_points integer, +cookie varchar(32), )` type Operation struct { - Order_number string `json:"number"` - Status string `json:"Status"` - Points uint `json:"accrual"` - Uploaded_at *time.Time `json:"uploaded_at"` - Users string `json:"-"` - //Operation string `json:"-"` // "accrual" or "withdraw" + Order_number string `json:"number"` + Status string `json:"Status"` + Points uint `json:"accrual"` + Uploaded_at string `json:"uploaded_at"` } const ( @@ -46,6 +49,16 @@ type DBI interface { CreateTable() error Ping(ctx context.Context) error Close() error + + WriteOrderAccrual(order string, user string) (err error) + ReadOrderAccrual(storage string, user string, order string) (op Operation, err error) + ReadAllOrderAccrualUser(storage string, user string) (ops []Operation, err error) + UpdateOrderAccrual(storage string, order string, user string) (status string, points uint, err error) + ReadUserPoints(storage string, user string) (u UserPoints, err error) + WithdrawnUserPoints(storage string, user string, order string, sum uint) (err error) + WriteOrderWithdrawn(order string, user string, point uint) (err error) + RegisterUser(login string, pass string) (hexCookie string, err error) + LoginUser(login string, pass string) (hexCookie string, err error) } type Database struct { @@ -105,7 +118,7 @@ func (db *Database) Close() error { // отловить ошибку с уникальным номером заказа, там где будет вызываться func (db *Database) WriteOrderAccrual(order string, user string) (err error) { - _, err = db.connection.Exec("insert into OperationsGopherMart (order_number, users, operation) values ($1,$2,$3)", order, user, accrual) + _, err = db.connection.Exec("insert into OperationsGopherMart (order_number, login, operation) values ($1,$2,$3)", order, user, accrual) if err != nil { return err } @@ -113,7 +126,7 @@ func (db *Database) WriteOrderAccrual(order string, user string) (err error) { } func (db *Database) ReadOrderAccrual(storage string, user string, order string) (op Operation, err error) { - row := db.connection.QueryRow("select order_number, status, uploaded_at, points from OperationsGopherMart where order_number = $1 and users = $2 and operation != $3", + row := db.connection.QueryRow("select order_number, status, uploaded_at, points from OperationsGopherMart where order_number = $1 and login = $2 and operation != $3", order, user, accrual) err = row.Scan(&op.Order_number, &op.Status, &op.Uploaded_at, &op.Points) if err != nil { @@ -131,7 +144,10 @@ func (db *Database) ReadOrderAccrual(storage string, user string, order string) // проверить ошибки, return func (db *Database) ReadAllOrderAccrualUser(storage string, user string) (ops []Operation, err error) { var op Operation - rows, err := db.connection.Query("select order_number, status, uploaded_at, points from OperationsGopherMart where users = $1 and operation != $2", user, accrual) + rows, err := db.connection.Query("select order_number, status, uploaded_at, points from OperationsGopherMart where login = $1 and operation != $2", user, accrual) + if err != nil { + return nil, err + } defer rows.Close() for rows.Next() { err := rows.Scan(&op.Order_number, &op.Status, &op.Uploaded_at, &op.Points) @@ -160,7 +176,7 @@ func (db *Database) UpdateOrderAccrual(storage string, order string, user string } //зачислить балы пользователю if status == registered { - _, err = db.connection.Exec("UPDATE UsersGopherMart SET current_points = current_points + $1 WHERE users=$2", orderStatus.Point, user) + _, err = db.connection.Exec("UPDATE UsersGopherMart SET current_points = current_points + $1 WHERE login=$2", orderStatus.Point, user) if err != nil { return "", 0, err } @@ -169,8 +185,8 @@ func (db *Database) UpdateOrderAccrual(storage string, order string, user string } type UserPoints struct { - CurrentPoints int `json:"current"` - WithdrawnPoints int `json:"withdrawn"` + CurrentPoints uint `json:"current"` + WithdrawnPoints uint `json:"withdrawn"` } // информация по счёту, но перед этим ReadAllOrderAccrualUser @@ -178,7 +194,7 @@ func (db *Database) ReadUserPoints(storage string, user string) (u UserPoints, e if _, err = db.ReadAllOrderAccrualUser(storage, user); err != nil { return UserPoints{}, err } - row := db.connection.QueryRow("select current_points, withdrawn_points from UsersGopherMart where users = $1", + row := db.connection.QueryRow("select current_points, withdrawn_points from UsersGopherMart where login = $1", user) if err = row.Scan(&u.CurrentPoints, &u.WithdrawnPoints); err != nil { return UserPoints{}, err @@ -188,7 +204,7 @@ func (db *Database) ReadUserPoints(storage string, user string) (u UserPoints, e // списание // попытка списать баллы, но перед этим ReadAllOrderAccrualUser -func (db *Database) WithdrawnUserPoints(storage string, user string, sum int) (err error) { +func (db *Database) WithdrawnUserPoints(storage string, user string, order string, sum uint) (err error) { var u UserPoints // if _, err = db.ReadAllOrderAccrualUser(storage, user); err != nil { @@ -198,18 +214,93 @@ func (db *Database) WithdrawnUserPoints(storage string, user string, sum int) (e if err != nil { return err } - if u.CurrentPoints >= sum { - // списываем и ретурн - _, err = db.connection.Exec("UPDATE UsersGopherMart SET current_points = current_points - $1 and withdrawn_points = withdrawn_points + $1 WHERE users=$2", - sum, user) - if err != nil { - return err - } - + if u.CurrentPoints < sum { + return errors.New("Not enough points") // отловить!!!!!!!!!!!!!!!!!!! } + // списываем и ретурн + _, err = db.connection.Exec("UPDATE UsersGopherMart SET current_points = current_points - $1 and withdrawn_points = withdrawn_points + $1 WHERE login=$2", + sum, user) + if err != nil { + return err + } + // добавление закаказа со списанием в таблицу + db.WriteOrderWithdrawn(order, user, sum) - // ошибка, недостаточно баллов и ретурн return nil } -// создание заказа со списанием +func (db *Database) WriteOrderWithdrawn(order string, user string, point uint) (err error) { + timeNow := time.Now().Format(time.RFC3339) + _, err = db.connection.Exec("insert into OperationsGopherMart (order_number, users, operation, points, uploaded_at) values ($1,$2,$3,$4,$5)", order, user, withdraw, point, timeNow) + if err != nil { + return err + } + return nil +} + +// регистрация +func (db *Database) RegisterUser(login string, pass string) (hexCookie string, err error) { + h := md5.New() + h.Write([]byte(login + pass)) + pass = hex.EncodeToString(h.Sum(nil)) + + _, err = db.connection.Exec("insert into UsersGopherMart (login, password, current_points, withdrawn_points ) values ($1,$2,$3,$3)", login, pass, 0) + if err != nil { + return "", err + } + + hexCookie, err = db.AutUser(login, pass) + if err != nil { + return "", err + } + return hexCookie, nil +} + +// авторизация +func (db *Database) LoginUser(login string, pass string) (hexCookie string, err error) { + h := md5.New() + h.Write([]byte(login + pass)) + pass = hex.EncodeToString(h.Sum(nil)) + var dbPass string + + //а что дальше то + row := db.connection.QueryRow("select password from UsersGopherMart where login = $1", + login) + if err = row.Scan(&dbPass); err != nil { + return "", err + } + if dbPass != pass { + return "", errors.New("Wrong password") + } + hexCookie, err = db.AutUser(login, pass) + if err != nil { + return "", err + } + + return hexCookie, nil +} + +func (db *Database) AutUser(login string, pass string) (hexCookie string, err error) { + token, err := CryptoToken([]byte(pass)) + if err != nil { + return "", err + } + hexCookie = hex.EncodeToString(token) + _, err = db.connection.Exec("UPDATE UsersGopherMart SET cookie = $1 WHERE login = $2", hexCookie, login) + if err != nil { + return "", err + } + + return hexCookie, nil +} + +func (db *Database) CheakLogin(HexCookie string, login string) (hexCookie string, err error) { + var s string + row := db.connection.QueryRow("select cookie from UsersGopherMart where login = $1", + login) + if err = row.Scan(&s); err != nil { + return "", err + } + if HexCookie != + return hexCookie, nil +} diff --git a/cmd/router/accrualEvent.go b/cmd/events/accrualEvent.go similarity index 71% rename from cmd/router/accrualEvent.go rename to cmd/events/accrualEvent.go index 71fff78eb..d08a99392 100644 --- a/cmd/router/accrualEvent.go +++ b/cmd/events/accrualEvent.go @@ -1,4 +1,4 @@ -package router +package events import ( "encoding/json" @@ -10,11 +10,18 @@ import ( "GopherMart/cmd/errorsGM" ) +type requestAccrualFloat struct { + Number string `json:"number"` + Status string `json:"Status"` + Point float64 `json:"accrual"` + UploadedAt string `json:"uploaded_at"` +} + type requestAccrual struct { - Number string `json:"number"` - Status string `json:"Status"` - Point uint `json:"accrual"` - UploadedAt *time.Time `json:"uploaded_at"` + Number string `json:"number"` + Status string `json:"Status"` + Point uint `json:"accrual"` + UploadedAt string `json:"uploaded_at"` } func accrualOrderStatus(storageAccrual string, order string) (bodyRequest requestAccrual, err error) { @@ -39,14 +46,19 @@ func accrualGet(storage string, order string) (bodyRequest requestAccrual, durat switch resp.StatusCode { case 200: + var bodyFloat requestAccrualFloat body, err := io.ReadAll(resp.Request.Body) if err != nil { return requestAccrual{}, 0, &errorsGM.ErrorGopherMart{errorsGM.ReadAllError, err} } - err = json.Unmarshal(body, &bodyRequest) + err = json.Unmarshal(body, &bodyFloat) if err != nil { return requestAccrual{}, 0, &errorsGM.ErrorGopherMart{errorsGM.UnmarshalError, err} } + bodyRequest.Status = bodyFloat.Status + bodyRequest.Point = uint(bodyFloat.Point * 100) + bodyRequest.UploadedAt = bodyFloat.UploadedAt + bodyRequest.Number = bodyFloat.Number return bodyRequest, 0, nil case 429: @@ -56,7 +68,7 @@ func accrualGet(storage string, order string) (bodyRequest requestAccrual, durat if err != nil { return requestAccrual{}, 0, &errorsGM.ErrorGopherMart{errorsGM.StatusTooManyRequests, err} } - return requestAccrual{}, sec, &errorsGM.ErrorGopherMart{errorsGM.StatusTooManyRequests, err} // + return requestAccrual{}, sec, nil // case 500: return requestAccrual{}, 0, &errorsGM.ErrorGopherMart{errorsGM.StatusInternalServerError, err} // diff --git a/cmd/router/mwCheakCookies.go b/cmd/router/mwCheakCookies.go new file mode 100644 index 000000000..ec320262a --- /dev/null +++ b/cmd/router/mwCheakCookies.go @@ -0,0 +1,17 @@ +package router + +import ( + "github.com/labstack/echo" +) + +func (s serverMart) CheakCookies(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + requestCookies, err := c.Request().Cookie("GopherMart") + if err != nil { + return nil + } + + s.db.CheakLogin(requestCookies.Value, requestCookies.Domain) + return nil + } +} diff --git a/cmd/router/postAPIUserLogin.go b/cmd/router/postAPIUserLogin.go new file mode 100644 index 000000000..8be7b614a --- /dev/null +++ b/cmd/router/postAPIUserLogin.go @@ -0,0 +1,35 @@ +package router + +import ( + "encoding/json" + "io" + "net/http" + + "github.com/labstack/echo" +) + +func (s *serverMart) postAPIUserLogin(c echo.Context) error { + var userLog register + defer c.Request().Body.Close() + body, err := io.ReadAll(c.Request().Body) + if err != nil { + c.Response().WriteHeader(http.StatusBadRequest) + return nil //400 + } + if err = json.Unmarshal(body, &userLog); err != nil { + c.Response().WriteHeader(http.StatusBadRequest) + return nil // 400 + } + + hexCookie, err := s.db.LoginUser(userLog.Login, userLog.Password) + if err != nil { + return err + } + + cookie := new(http.Cookie) + cookie.Name = "GopherMart" + cookie.Value = hexCookie + c.SetCookie(cookie) + + return nil +} diff --git a/cmd/router/postAPIUserRegister.go b/cmd/router/postAPIUserRegister.go new file mode 100644 index 000000000..e61389c50 --- /dev/null +++ b/cmd/router/postAPIUserRegister.go @@ -0,0 +1,54 @@ +package router + +import ( + "encoding/json" + "io" + "net/http" + + "github.com/jackc/pgerrcode" + "github.com/jackc/pgx/v5/pgconn" + "github.com/labstack/echo" + "github.com/pkg/errors" +) + +type register struct { + Login string `json:"login"` + Password string `json:"password"` +} + +func (s *serverMart) postAPIUserRegister(c echo.Context) error { + var userLog register + defer c.Request().Body.Close() + body, err := io.ReadAll(c.Request().Body) + if err != nil { + c.Response().WriteHeader(http.StatusBadRequest) + return nil //400 + } + if err = json.Unmarshal(body, &userLog); err != nil { + c.Response().WriteHeader(http.StatusBadRequest) + return nil // 400 + } + + var pgErr *pgconn.PgError + + hexCookie, err := s.db.RegisterUser(userLog.Login, userLog.Password) + if errors.As(err, &pgErr) { + switch pgErr.Code { + case pgerrcode.UniqueViolation: // дубликат + c.Response().WriteHeader(http.StatusConflict) + return nil + default: + c.Response().WriteHeader(http.StatusInternalServerError) + return nil + } + } + + cookie := new(http.Cookie) + cookie.Name = "GopherMart" + cookie.Value = hexCookie + cookie.Domain = userLog.Login + c.SetCookie(cookie) + + c.Response().WriteHeader(http.StatusOK) + return nil +} diff --git a/cmd/router/router.go b/cmd/router/router.go index b366eb2dd..55d0b945e 100644 --- a/cmd/router/router.go +++ b/cmd/router/router.go @@ -5,18 +5,20 @@ import ( "github.com/caarlos0/env" "github.com/labstack/echo" + + "GopherMart/cmd/events" ) -type ConfigURL struct { +type Config struct { ServerAddress string `env:"RUN_ADDRESS"` BDAddress string `env:"DATABASE_URI"` AccrualAddress string `env:"ACCRUAL_SYSTEM_ADDRESS"` } type serverMart struct { - cfg ConfigURL + cfg Config serv *echo.Echo - db DBI + db events.DBI } func InitServer() *serverMart { @@ -34,12 +36,12 @@ func (s serverMart) Router() error { e := echo.New() e.Use(s.gzip) - // - //e.POST("/api/user/register", s.postAPIUserRegister) - //e.POST("/api/user/login", s.postAPIUserLogin) - // - //e.Use(s.CheakCookies) - // + + e.POST("/api/user/register", s.postAPIUserRegister) + e.POST("/api/user/login", s.postAPIUserLogin) + + e.Use(s.CheakCookies) + //e.GET("/api/user/orders", s.getAPIUserOrders) //e.GET("/api/user/balance", s.getAPIUserBalance) //e.GET("/api/user/withdrawals", s.getAPIUserWithdrawals) @@ -71,7 +73,7 @@ func (s *serverMart) parseFlagCfg() error { func (s serverMart) connectDB() error { var err error - if s.db, err = InitDB(); err != nil { + if s.db, err = events.InitDB(); err != nil { return err } if err = s.db.Connect(s.cfg.BDAddress); err != nil { diff --git a/go.mod b/go.mod index 0c6a40cf5..1909c950d 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,11 @@ go 1.19 require ( github.com/caarlos0/env v3.5.0+incompatible // indirect + github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect github.com/jackc/pgx v3.6.2+incompatible // indirect + github.com/jackc/pgx/v5 v5.2.0 // indirect github.com/labstack/echo v3.3.10+incompatible // indirect github.com/labstack/gommon v0.4.0 // indirect github.com/mattn/go-colorable v0.1.11 // indirect From 0171cc60260f99ca8be47a3006c279be3b8bea74 Mon Sep 17 00:00:00 2001 From: bbrodriges Date: Mon, 13 Feb 2023 13:17:56 +0300 Subject: [PATCH 3/6] Update gophermart.yml --- .github/workflows/gophermart.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gophermart.yml b/.github/workflows/gophermart.yml index 2a2d592e6..800415414 100644 --- a/.github/workflows/gophermart.yml +++ b/.github/workflows/gophermart.yml @@ -46,7 +46,7 @@ jobs: - name: Prepare binaries run: | - (cd cmd/gophermart && go build -o gophermart) + (cd cmd/gophermart && go build -buildvcs=false -o gophermart) (cd cmd/accrual && chmod +x accrual_linux_amd64) - name: Test From 6b74b818d22422d29e5145a2d10c4f7dae10f1f6 Mon Sep 17 00:00:00 2001 From: bbrodriges Date: Tue, 14 Feb 2023 13:11:08 +0300 Subject: [PATCH 4/6] Update SPECIFICATION.md --- SPECIFICATION.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SPECIFICATION.md b/SPECIFICATION.md index 8944724a3..6bfe2e009 100644 --- a/SPECIFICATION.md +++ b/SPECIFICATION.md @@ -346,6 +346,8 @@ Content-Length: 0 - `accrual` — рассчитанные баллы к начислению, при отсутствии начисления — поле отсутствует в ответе. +- `204` - заказ не зарегистрирован в системе расчета. + - `429` — превышено количество запросов к сервису. Формат ответа: From 3f00a559410a4617bb3f90cb111be8813fc52704 Mon Sep 17 00:00:00 2001 From: Demosfenys Date: Wed, 15 Feb 2023 18:35:40 +0300 Subject: [PATCH 5/6] =?UTF-8?q?=D0=9D=D0=B0=D0=B1=D1=80=D0=BE=D1=81=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=BE=D0=B1=D1=80=D0=B0=D1=89=D0=B5=D0=BD=D0=B8=D0=B9?= =?UTF-8?q?=20=D0=BA=20=D0=91=D0=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/events/Crypt.go | 31 -- cmd/events/Database.go | 306 ----------------- cmd/events/accrualEvent.go | 78 ----- cmd/gophermart/main.go | 2 +- cmd/router/mwCheakCookies.go | 17 - cmd/router/postAPIUserLogin.go | 35 -- go.mod | 1 + go.sum | 50 +++ {cmd => internal}/errorsGM/errorGopherMart.go | 11 + internal/events/Database.go | 314 ++++++++++++++++++ internal/events/Luna.go | 24 ++ internal/events/accrualEvent.go | 61 ++++ internal/events/eventsJWT.go | 52 +++ internal/router/getAPIUserBalance.go | 27 ++ internal/router/getAPIUserOrders.go | 31 ++ internal/router/getAPIUserWithdrawals.go | 31 ++ .../gzip.go => internal/router/mwGzip.go | 0 internal/router/mwUserAuthentication.go | 39 +++ internal/router/postAPIUserBalanceWithdraw.go | 58 ++++ internal/router/postAPIUserLogin.go | 40 +++ internal/router/postAPIUserOrders.go | 47 +++ .../router/postAPIUserRegister.go | 26 +- {cmd => internal}/router/router.go | 20 +- internal/router/update.go | 49 +++ 24 files changed, 860 insertions(+), 490 deletions(-) delete mode 100644 cmd/events/Crypt.go delete mode 100644 cmd/events/Database.go delete mode 100644 cmd/events/accrualEvent.go delete mode 100644 cmd/router/mwCheakCookies.go delete mode 100644 cmd/router/postAPIUserLogin.go create mode 100644 go.sum rename {cmd => internal}/errorsGM/errorGopherMart.go (59%) create mode 100644 internal/events/Database.go create mode 100644 internal/events/Luna.go create mode 100644 internal/events/accrualEvent.go create mode 100644 internal/events/eventsJWT.go create mode 100644 internal/router/getAPIUserBalance.go create mode 100644 internal/router/getAPIUserOrders.go create mode 100644 internal/router/getAPIUserWithdrawals.go rename cmd/router/gzip.go => internal/router/mwGzip.go (100%) create mode 100644 internal/router/mwUserAuthentication.go create mode 100644 internal/router/postAPIUserBalanceWithdraw.go create mode 100644 internal/router/postAPIUserLogin.go create mode 100644 internal/router/postAPIUserOrders.go rename {cmd => internal}/router/postAPIUserRegister.go (67%) rename {cmd => internal}/router/router.go (63%) create mode 100644 internal/router/update.go diff --git a/cmd/events/Crypt.go b/cmd/events/Crypt.go deleted file mode 100644 index eaea7208b..000000000 --- a/cmd/events/Crypt.go +++ /dev/null @@ -1,31 +0,0 @@ -package events - -import ( - "crypto/aes" - "log" - "time" -) - -var key = append([]byte(time.Now().Format("02-01-2006 15")), []byte(":00")...) - -func CryptoToken(token []byte) ([]byte, error) { - aesBlock, err := aes.NewCipher(key) - if err != nil { - log.Fatal(err) - return nil, err - } - cryptoString := make([]byte, len(token)) - aesBlock.Encrypt(cryptoString, token) - return cryptoString, nil -} - -func DeCryptoToken(token []byte) ([]byte, error) { - aesBlock, err := aes.NewCipher(key) - if err != nil { - log.Fatal(err) - return nil, err - } - deCryptoString := make([]byte, len(token)) - aesBlock.Decrypt(deCryptoString, token) - return deCryptoString, nil -} diff --git a/cmd/events/Database.go b/cmd/events/Database.go deleted file mode 100644 index 6abce5594..000000000 --- a/cmd/events/Database.go +++ /dev/null @@ -1,306 +0,0 @@ -package events - -import ( - "context" - "crypto/md5" - "database/sql" - "encoding/hex" - "time" - - "github.com/pkg/errors" -) - -var CreateTableOperations = `CREATE TABLE OperationsGopherMart( -order_number varchar(32), -login varchar(64), -uploaded_at varchar(32), -status varchar(32), -operation varchar(32), -points integer, -)` - -var CreateTableUsers = `CREATE TABLE UsersGopherMart( -login varchar(32), -password varchar(64), -current_points integer, -withdrawn_points integer, -cookie varchar(32), -)` - -type Operation struct { - Order_number string `json:"number"` - Status string `json:"Status"` - Points uint `json:"accrual"` - Uploaded_at string `json:"uploaded_at"` -} - -const ( - accrual = "accrual" - withdraw = "withdraw" - - processing = "PROCESSING" - registered = "REGISTERED" - neworder = "NEW" - invalid = "INVALID" -) - -type DBI interface { - Connect(connStr string) (err error) - CreateTable() error - Ping(ctx context.Context) error - Close() error - - WriteOrderAccrual(order string, user string) (err error) - ReadOrderAccrual(storage string, user string, order string) (op Operation, err error) - ReadAllOrderAccrualUser(storage string, user string) (ops []Operation, err error) - UpdateOrderAccrual(storage string, order string, user string) (status string, points uint, err error) - ReadUserPoints(storage string, user string) (u UserPoints, err error) - WithdrawnUserPoints(storage string, user string, order string, sum uint) (err error) - WriteOrderWithdrawn(order string, user string, point uint) (err error) - RegisterUser(login string, pass string) (hexCookie string, err error) - LoginUser(login string, pass string) (hexCookie string, err error) -} - -type Database struct { - connection *sql.DB -} - -func InitDB() (*Database, error) { - return &Database{}, nil -} - -func (db *Database) Connect(connStr string) (err error) { - db.connection, err = sql.Open("pgx", connStr) - if err != nil { - return err - } - if err = db.CreateTable(); err != nil { - return err - } - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) - defer cancel() - if err = db.Ping(ctx); err != nil { - return err - } - return nil -} - -func (db *Database) CreateTable() error { - if _, err := db.connection.Exec("Drop TABLE OperationsGopherMart"); err != nil { - return err - } - if _, err := db.connection.Exec("Drop TABLE UsersGopherMart"); err != nil { - return err - } - if _, err := db.connection.Exec(CreateTableOperations); err != nil { - return err - } - if _, err := db.connection.Exec("CREATE UNIQUE INDEX order_index ON OperationsGopherMart (order_number)"); err != nil { - return err - } - if _, err := db.connection.Exec(CreateTableUsers); err != nil { - return err - } - _, err := db.connection.Exec("CREATE UNIQUE INDEX login_index ON UsersGopherMart (login,users)") - return err -} - -func (db *Database) Ping(ctx context.Context) error { - if err := db.connection.PingContext(ctx); err != nil { - return err - } - return nil -} - -func (db *Database) Close() error { - return db.connection.Close() -} - -// отловить ошибку с уникальным номером заказа, там где будет вызываться -func (db *Database) WriteOrderAccrual(order string, user string) (err error) { - _, err = db.connection.Exec("insert into OperationsGopherMart (order_number, login, operation) values ($1,$2,$3)", order, user, accrual) - if err != nil { - return err - } - return nil -} - -func (db *Database) ReadOrderAccrual(storage string, user string, order string) (op Operation, err error) { - row := db.connection.QueryRow("select order_number, status, uploaded_at, points from OperationsGopherMart where order_number = $1 and login = $2 and operation != $3", - order, user, accrual) - err = row.Scan(&op.Order_number, &op.Status, &op.Uploaded_at, &op.Points) - if err != nil { - return Operation{}, err - } - if (op.Status == neworder) || (op.Status == processing) { - op.Status, op.Points, err = db.UpdateOrderAccrual(storage, op.Order_number, user) - if err != nil { - return Operation{}, err - } - } - return op, nil -} - -// проверить ошибки, return -func (db *Database) ReadAllOrderAccrualUser(storage string, user string) (ops []Operation, err error) { - var op Operation - rows, err := db.connection.Query("select order_number, status, uploaded_at, points from OperationsGopherMart where login = $1 and operation != $2", user, accrual) - if err != nil { - return nil, err - } - defer rows.Close() - for rows.Next() { - err := rows.Scan(&op.Order_number, &op.Status, &op.Uploaded_at, &op.Points) - if err != nil { - return nil, err - } - if (op.Status == neworder) || (op.Status == processing) { - op.Status, op.Points, err = db.UpdateOrderAccrual(storage, op.Order_number, user) - if err != nil { - return nil, err - } - } - ops = append(ops, op) - } - return ops, nil -} - -func (db *Database) UpdateOrderAccrual(storage string, order string, user string) (status string, points uint, err error) { - orderStatus, err := accrualOrderStatus(storage, order) - if err != nil { - return "", 0, err - } - _, err = db.connection.Exec("UPDATE OperationsGopherMart SET status = $1,point = $2 WHERE order=$3", orderStatus.Status, orderStatus.Point, order) - if err != nil { - return "", 0, err - } - //зачислить балы пользователю - if status == registered { - _, err = db.connection.Exec("UPDATE UsersGopherMart SET current_points = current_points + $1 WHERE login=$2", orderStatus.Point, user) - if err != nil { - return "", 0, err - } - } - return orderStatus.Status, orderStatus.Point, nil -} - -type UserPoints struct { - CurrentPoints uint `json:"current"` - WithdrawnPoints uint `json:"withdrawn"` -} - -// информация по счёту, но перед этим ReadAllOrderAccrualUser -func (db *Database) ReadUserPoints(storage string, user string) (u UserPoints, err error) { - if _, err = db.ReadAllOrderAccrualUser(storage, user); err != nil { - return UserPoints{}, err - } - row := db.connection.QueryRow("select current_points, withdrawn_points from UsersGopherMart where login = $1", - user) - if err = row.Scan(&u.CurrentPoints, &u.WithdrawnPoints); err != nil { - return UserPoints{}, err - } - return u, nil -} - -// списание -// попытка списать баллы, но перед этим ReadAllOrderAccrualUser -func (db *Database) WithdrawnUserPoints(storage string, user string, order string, sum uint) (err error) { - var u UserPoints - // - if _, err = db.ReadAllOrderAccrualUser(storage, user); err != nil { - return err - } - u, err = db.ReadUserPoints(storage, user) - if err != nil { - return err - } - if u.CurrentPoints < sum { - return errors.New("Not enough points") // отловить!!!!!!!!!!!!!!!!!!! - } - // списываем и ретурн - _, err = db.connection.Exec("UPDATE UsersGopherMart SET current_points = current_points - $1 and withdrawn_points = withdrawn_points + $1 WHERE login=$2", - sum, user) - if err != nil { - return err - } - // добавление закаказа со списанием в таблицу - db.WriteOrderWithdrawn(order, user, sum) - - return nil -} - -func (db *Database) WriteOrderWithdrawn(order string, user string, point uint) (err error) { - timeNow := time.Now().Format(time.RFC3339) - _, err = db.connection.Exec("insert into OperationsGopherMart (order_number, users, operation, points, uploaded_at) values ($1,$2,$3,$4,$5)", order, user, withdraw, point, timeNow) - if err != nil { - return err - } - return nil -} - -// регистрация -func (db *Database) RegisterUser(login string, pass string) (hexCookie string, err error) { - h := md5.New() - h.Write([]byte(login + pass)) - pass = hex.EncodeToString(h.Sum(nil)) - - _, err = db.connection.Exec("insert into UsersGopherMart (login, password, current_points, withdrawn_points ) values ($1,$2,$3,$3)", login, pass, 0) - if err != nil { - return "", err - } - - hexCookie, err = db.AutUser(login, pass) - if err != nil { - return "", err - } - return hexCookie, nil -} - -// авторизация -func (db *Database) LoginUser(login string, pass string) (hexCookie string, err error) { - h := md5.New() - h.Write([]byte(login + pass)) - pass = hex.EncodeToString(h.Sum(nil)) - var dbPass string - - //а что дальше то - row := db.connection.QueryRow("select password from UsersGopherMart where login = $1", - login) - if err = row.Scan(&dbPass); err != nil { - return "", err - } - if dbPass != pass { - return "", errors.New("Wrong password") - } - hexCookie, err = db.AutUser(login, pass) - if err != nil { - return "", err - } - - return hexCookie, nil -} - -func (db *Database) AutUser(login string, pass string) (hexCookie string, err error) { - token, err := CryptoToken([]byte(pass)) - if err != nil { - return "", err - } - hexCookie = hex.EncodeToString(token) - _, err = db.connection.Exec("UPDATE UsersGopherMart SET cookie = $1 WHERE login = $2", hexCookie, login) - if err != nil { - return "", err - } - - return hexCookie, nil -} - -func (db *Database) CheakLogin(HexCookie string, login string) (hexCookie string, err error) { - var s string - row := db.connection.QueryRow("select cookie from UsersGopherMart where login = $1", - login) - if err = row.Scan(&s); err != nil { - return "", err - } - if HexCookie != - return hexCookie, nil -} diff --git a/cmd/events/accrualEvent.go b/cmd/events/accrualEvent.go deleted file mode 100644 index d08a99392..000000000 --- a/cmd/events/accrualEvent.go +++ /dev/null @@ -1,78 +0,0 @@ -package events - -import ( - "encoding/json" - "io" - "net/http" - "strconv" - "time" - - "GopherMart/cmd/errorsGM" -) - -type requestAccrualFloat struct { - Number string `json:"number"` - Status string `json:"Status"` - Point float64 `json:"accrual"` - UploadedAt string `json:"uploaded_at"` -} - -type requestAccrual struct { - Number string `json:"number"` - Status string `json:"Status"` - Point uint `json:"accrual"` - UploadedAt string `json:"uploaded_at"` -} - -func accrualOrderStatus(storageAccrual string, order string) (bodyRequest requestAccrual, err error) { - var errGet *errorsGM.ErrorGopherMart - accrual, sec, errGet := accrualGet(storageAccrual, order) - for ; sec != 0; accrual, sec, err = accrualGet(storageAccrual, order) { - time.Sleep(time.Duration(sec) * time.Second) - } - if errGet != nil { - return requestAccrual{}, errGet - } - - return accrual, nil -} - -func accrualGet(storage string, order string) (bodyRequest requestAccrual, duration int64, errGM *errorsGM.ErrorGopherMart) { - get := storage + "/api/orders/" + order - resp, err := http.Get(get) - if err != nil { - return requestAccrual{}, 0, &errorsGM.ErrorGopherMart{errorsGM.UnmarshalError, err} - } - switch resp.StatusCode { - - case 200: - var bodyFloat requestAccrualFloat - body, err := io.ReadAll(resp.Request.Body) - if err != nil { - return requestAccrual{}, 0, &errorsGM.ErrorGopherMart{errorsGM.ReadAllError, err} - } - err = json.Unmarshal(body, &bodyFloat) - if err != nil { - return requestAccrual{}, 0, &errorsGM.ErrorGopherMart{errorsGM.UnmarshalError, err} - } - bodyRequest.Status = bodyFloat.Status - bodyRequest.Point = uint(bodyFloat.Point * 100) - bodyRequest.UploadedAt = bodyFloat.UploadedAt - bodyRequest.Number = bodyFloat.Number - return bodyRequest, 0, nil - - case 429: - header := resp.Header - a := header["Retry-After"][0] - sec, err := strconv.ParseInt(a, 10, 0) - if err != nil { - return requestAccrual{}, 0, &errorsGM.ErrorGopherMart{errorsGM.StatusTooManyRequests, err} - } - return requestAccrual{}, sec, nil // - - case 500: - return requestAccrual{}, 0, &errorsGM.ErrorGopherMart{errorsGM.StatusInternalServerError, err} // - } - - return requestAccrual{}, 0, &errorsGM.ErrorGopherMart{errorsGM.RespStatusCodeNotMatch, err} -} diff --git a/cmd/gophermart/main.go b/cmd/gophermart/main.go index f156021b4..ef2c5981b 100644 --- a/cmd/gophermart/main.go +++ b/cmd/gophermart/main.go @@ -3,7 +3,7 @@ package main import ( "log" - "GopherMart/cmd/router" + "GopherMart/internal/router" ) func main() { diff --git a/cmd/router/mwCheakCookies.go b/cmd/router/mwCheakCookies.go deleted file mode 100644 index ec320262a..000000000 --- a/cmd/router/mwCheakCookies.go +++ /dev/null @@ -1,17 +0,0 @@ -package router - -import ( - "github.com/labstack/echo" -) - -func (s serverMart) CheakCookies(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - requestCookies, err := c.Request().Cookie("GopherMart") - if err != nil { - return nil - } - - s.db.CheakLogin(requestCookies.Value, requestCookies.Domain) - return nil - } -} diff --git a/cmd/router/postAPIUserLogin.go b/cmd/router/postAPIUserLogin.go deleted file mode 100644 index 8be7b614a..000000000 --- a/cmd/router/postAPIUserLogin.go +++ /dev/null @@ -1,35 +0,0 @@ -package router - -import ( - "encoding/json" - "io" - "net/http" - - "github.com/labstack/echo" -) - -func (s *serverMart) postAPIUserLogin(c echo.Context) error { - var userLog register - defer c.Request().Body.Close() - body, err := io.ReadAll(c.Request().Body) - if err != nil { - c.Response().WriteHeader(http.StatusBadRequest) - return nil //400 - } - if err = json.Unmarshal(body, &userLog); err != nil { - c.Response().WriteHeader(http.StatusBadRequest) - return nil // 400 - } - - hexCookie, err := s.db.LoginUser(userLog.Login, userLog.Password) - if err != nil { - return err - } - - cookie := new(http.Cookie) - cookie.Name = "GopherMart" - cookie.Value = hexCookie - c.SetCookie(cookie) - - return nil -} diff --git a/go.mod b/go.mod index 1909c950d..4c042bd35 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.19 require ( github.com/caarlos0/env v3.5.0+incompatible // indirect + github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 000000000..4c5568680 --- /dev/null +++ b/go.sum @@ -0,0 +1,50 @@ +github.com/caarlos0/env v3.5.0+incompatible h1:Yy0UN8o9Wtr/jGHZDpCBLpNrzcFLLM2yixi/rBrKyJs= +github.com/caarlos0/env v3.5.0+incompatible/go.mod h1:tdCsowwCzMLdkqRYDlHpZCp2UooDD3MspDBjZ2AD02Y= +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/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa h1:s+4MhCQ6YrzisK6hFJUX53drDT4UsSW3DEhKn0ifuHw= +github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= +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-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgx v3.6.2+incompatible h1:2zP5OD7kiyR3xzRYMhOcXVvkDZsImVXfj+yIyTQf3/o= +github.com/jackc/pgx v3.6.2+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= +github.com/jackc/pgx/v5 v5.2.0 h1:NdPpngX0Y6z6XDFKqmFQaE+bCtkqzvQIOt1wvBlAqs8= +github.com/jackc/pgx/v5 v5.2.0/go.mod h1:Ptn7zmohNsWEsdxRawMzk3gaKma2obW+NWTnKa0S4nk= +github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg= +github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s= +github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= +github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= +github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs= +github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4= +github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= +golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= +golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/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= diff --git a/cmd/errorsGM/errorGopherMart.go b/internal/errorsGM/errorGopherMart.go similarity index 59% rename from cmd/errorsGM/errorGopherMart.go rename to internal/errorsGM/errorGopherMart.go index e195f7fcc..db55584e9 100644 --- a/cmd/errorsGM/errorGopherMart.go +++ b/internal/errorsGM/errorGopherMart.go @@ -1,5 +1,9 @@ package errorsGM +import ( + "fmt" +) + type ErrorGopherMart struct { Code string Err error @@ -31,3 +35,10 @@ const ( //WTF?? RespStatusCodeNotMatch = "WTF" ) + +var ( + ErrLoadedEarlierThisUser = fmt.Errorf("order was loaded earlier by this user") + ErrLoadedEarlierAnotherUser = fmt.Errorf("order was loaded earlier by another user") + ErrDontHavePoints = fmt.Errorf("not enough points") + ErrAccrualGetError = fmt.Errorf("error get in accular") +) diff --git a/internal/events/Database.go b/internal/events/Database.go new file mode 100644 index 000000000..70ceea9e8 --- /dev/null +++ b/internal/events/Database.go @@ -0,0 +1,314 @@ +package events + +import ( + "context" + "crypto/md5" + "database/sql" + "encoding/hex" + "time" + + "GopherMart/internal/errorsGM" +) + +var CreateTableOperations = `CREATE TABLE OperationsGopherMart( +order_number varchar(32), +login varchar(64), +uploaded_at varchar(32), +status varchar(32), +operation varchar(32), +points integer, +)` + +var CreateTableUsers = `CREATE TABLE UsersGopherMart( +login varchar(32), +password varchar(64), +current_points integer, +withdrawn_points integer, +cookie varchar(32), +)` + +type Operation struct { + Order_number string `json:"order"` + Status string `json:"status"` + Points float64 `json:"accrual"` + Uploaded_at string `json:"uploaded_at"` +} + +const ( + accrual = "accrual" + withdraw = "withdraw" + + newOrder = "NEW" + processing = "PROCESSING" + registered = "REGISTERED" + invalid = "INVALID" +) + +type DBI interface { + Connect(connStr string) (err error) + CreateTable() error + Ping(ctx context.Context) error + Close() error + + RegisterUser(login string, pass string) (tokenJWT string, err error) + LoginUser(login string, pass string) (tokenJWT string, err error) + + WriteOrderAccrual(order string, user string) (err error) + ReadAllOrderAccrualUser(user string) (ops []Operation, err error) + ReadUserPoints(user string) (u UserPoints, err error) + WithdrawnUserPoints(user string, order string, sum float64) (err error) + WriteOrderWithdrawn(order string, user string, point float64) (err error) + ReadAllOrderWithdrawnUser(user string) (ops []Operation, err error) + + ReadAllOrderAccrualNoComplite() (orders []orderstruct, err error) + UpdateOrderAccrual(login string, orderAccrual requestAccrual) (err error) +} + +type Database struct { + connection *sql.DB +} + +func InitDB() (*Database, error) { + return &Database{}, nil +} + +func (db *Database) Connect(connStr string) (err error) { + db.connection, err = sql.Open("pgx", connStr) + if err != nil { + return err + } + if err = db.CreateTable(); err != nil { + return err + } + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + if err = db.Ping(ctx); err != nil { + return err + } + return nil +} + +func (db *Database) CreateTable() error { + if _, err := db.connection.Exec("Drop TABLE OperationsGopherMart"); err != nil { + return err + } + if _, err := db.connection.Exec("Drop TABLE UsersGopherMart"); err != nil { + return err + } + if _, err := db.connection.Exec(CreateTableOperations); err != nil { + return err + } + if _, err := db.connection.Exec("CREATE UNIQUE INDEX order_index ON OperationsGopherMart (order_number)"); err != nil { + return err + } + if _, err := db.connection.Exec(CreateTableUsers); err != nil { + return err + } + _, err := db.connection.Exec("CREATE UNIQUE INDEX login_index ON UsersGopherMart (Login,users)") + return err +} + +func (db *Database) Ping(ctx context.Context) error { + if err := db.connection.PingContext(ctx); err != nil { + return err + } + return nil +} + +func (db *Database) Close() error { + return db.connection.Close() +} + +// добавление заказа для начисления +func (db *Database) WriteOrderAccrual(order string, user string) (err error) { + timeNow := time.Now().Format(time.RFC3339) + + var loginOrder string + row := db.connection.QueryRow("select login from OperationsGopherMart where order_number = $1", + order) + if err = row.Scan(&loginOrder); err != nil { + return err + } + if loginOrder != "" { + if loginOrder == user { + return errorsGM.ErrLoadedEarlierThisUser // надо что то вернуть + } + return errorsGM.ErrLoadedEarlierAnotherUser + } + _, err = db.connection.Exec("insert into OperationsGopherMart (order_number, Login, operation, uploaded_at) values ($1,$2,$3,$4)", order, user, accrual, timeNow) + if err != nil { + return err + } + return nil +} + +// вывод всех заказов пользователя +func (db *Database) ReadAllOrderAccrualUser(user string) (ops []Operation, err error) { + var op Operation + rows, err := db.connection.Query("select order_number, status, uploaded_at, points from OperationsGopherMart where login = $1 and operation != $2", user, accrual) + if err != nil { + return nil, err + } + defer rows.Close() + for rows.Next() { + err := rows.Scan(&op.Order_number, &op.Status, &op.Uploaded_at, &op.Points) + if err != nil { + return nil, err + } + op.Points = op.Points / 100 + ops = append(ops, op) + } + return ops, nil +} + +type UserPoints struct { + CurrentPoints float64 `json:"current"` + WithdrawnPoints float64 `json:"withdrawn"` +} + +// информация о потраченных и остатках баллов +func (db *Database) ReadUserPoints(user string) (up UserPoints, err error) { + row := db.connection.QueryRow("select current_points, withdrawn_points from UsersGopherMart where login = $1", + user) + if err = row.Scan(&up.CurrentPoints, &up.WithdrawnPoints); err != nil { + return UserPoints{}, err + } + up.WithdrawnPoints = up.WithdrawnPoints / 100 + up.CurrentPoints = up.CurrentPoints / 100 + return up, nil +} + +// списание +func (db *Database) WithdrawnUserPoints(user string, order string, sum float64) (err error) { + var u UserPoints + + u, err = db.ReadUserPoints(user) + if err != nil { + return err + } + if u.CurrentPoints < sum { + return errorsGM.ErrDontHavePoints + } + + err = db.WriteOrderWithdrawn(user, order, sum) + if err != nil { + return err + } + + _, err = db.connection.Exec("UPDATE UsersGopherMart SET current_points = current_points - $1 and withdrawn_points = withdrawn_points + $1 WHERE login=$2", + sum*100, user) + if err != nil { + return err + } + + return nil +} + +func (db *Database) WriteOrderWithdrawn(user string, order string, point float64) (err error) { + timeNow := time.Now().Format(time.RFC3339) + _, err = db.connection.Exec("insert into OperationsGopherMart (order_number, users, operation, points, uploaded_at) values ($1,$2,$3,$4,$5)", + order, user, withdraw, point*100, timeNow) + if err != nil { + return err + } + return nil +} + +func (db *Database) ReadAllOrderWithdrawnUser(user string) (ops []Operation, err error) { + var op Operation + rows, err := db.connection.Query("select order_number, status, uploaded_at, points from OperationsGopherMart where login = $1 and operation != $2", user, withdraw) + if err != nil { + return nil, err + } + defer rows.Close() + for rows.Next() { + err := rows.Scan(&op.Order_number, &op.Status, &op.Uploaded_at, &op.Points) + if err != nil { + return nil, err + } + op.Points = op.Points / 100 + ops = append(ops, op) + } + return ops, nil +} + +// регистрация +func (db *Database) RegisterUser(login string, pass string) (tokenJWT string, err error) { + h := md5.New() + h.Write([]byte(pass)) + passHex := hex.EncodeToString(h.Sum(nil)) + + _, err = db.connection.Exec("insert into UsersGopherMart (login, password, current_points, withdrawn_points ) values ($1,$2,$3,$3)", login, passHex, 0) + if err != nil { + return "", err + } + + tokenJWT, err = EncodeJWT(login) + if err != nil { + return "", err + } + return tokenJWT, nil +} + +// авторизация +func (db *Database) LoginUser(login string, pass string) (tokenJWT string, err error) { + h := md5.New() + h.Write([]byte(pass)) + pass = hex.EncodeToString(h.Sum(nil)) + var dbPass string + + row := db.connection.QueryRow("select password from UsersGopherMart where login = $1", + login) + if err = row.Scan(&dbPass); err != nil { + return "", err + } + if dbPass != pass { + return "", nil + } + tokenJWT, err = EncodeJWT(login) + if err != nil { + return "", err + } + return tokenJWT, nil +} + +type orderstruct struct { + Order string + Login string +} + +func (db *Database) ReadAllOrderAccrualNoComplite() (orders []orderstruct, err error) { + var order orderstruct + rows, err := db.connection.Query("select order_number,login from OperationsGopherMart where status = $1 or $2", + newOrder, processing) + if err != nil { + return nil, err + } + defer rows.Close() + for rows.Next() { + err := rows.Scan(&order.Order, &order.Login) + if err != nil { + return nil, err + } + orders = append(orders, order) + } + return orders, nil +} + +func (db *Database) UpdateOrderAccrual(login string, orderAccrual requestAccrual) (err error) { + + _, err = db.connection.Exec("UPDATE OperationsGopherMart SET status = $1,point = $2 WHERE Order=$3", + orderAccrual.Status, orderAccrual.Accrual, orderAccrual.Order) + if err != nil { + return err + } + //зачислить балы пользователю + if orderAccrual.Status == registered { + _, err = db.connection.Exec("UPDATE UsersGopherMart SET current_points = current_points + $1 WHERE Login=$2", + orderAccrual.Accrual, login) + if err != nil { + return err + } + } + return nil +} diff --git a/internal/events/Luna.go b/internal/events/Luna.go new file mode 100644 index 000000000..43b2bc989 --- /dev/null +++ b/internal/events/Luna.go @@ -0,0 +1,24 @@ +package events + +func Valid(number int) bool { + return (number%10+checksum(number/10))%10 == 0 +} + +func checksum(number int) int { + var luhn int + + for i := 0; number > 0; i++ { + cur := number % 10 + + if i%2 == 0 { // even + cur = cur * 2 + if cur > 9 { + cur = cur%10 + cur/10 + } + } + + luhn += cur + number = number / 10 + } + return luhn % 10 +} diff --git a/internal/events/accrualEvent.go b/internal/events/accrualEvent.go new file mode 100644 index 000000000..7448d133a --- /dev/null +++ b/internal/events/accrualEvent.go @@ -0,0 +1,61 @@ +package events + +import ( + "encoding/json" + "io" + "net/http" + "strconv" + + "GopherMart/internal/errorsGM" +) + +type requestAccrualFloat struct { + Order string `json:"order"` + Status string `json:"status"` + Accrual float64 `json:"accrual"` +} + +type requestAccrual struct { + Order string `json:"order"` + Status string `json:"status"` + Accrual uint `json:"accrual"` +} + +func AccrualGet(storage string, order string) (bodyUint requestAccrual, duration int64, err error) { + get := storage + "/api/orders/" + order + resp, err := http.Get(get) + if err != nil { + return requestAccrual{}, 0, &errorsGM.ErrorGopherMart{errorsGM.UnmarshalError, err} + } + switch resp.StatusCode { + + case 200: + var bodyFloat requestAccrualFloat + body, err := io.ReadAll(resp.Request.Body) + if err != nil { + return requestAccrual{}, 0, errorsGM.ErrAccrualGetError + } + err = json.Unmarshal(body, &bodyFloat) + if err != nil { + return requestAccrual{}, 0, errorsGM.ErrAccrualGetError + } + bodyUint.Status = bodyFloat.Status + bodyUint.Accrual = uint(bodyFloat.Accrual * 100) + bodyUint.Order = bodyFloat.Order + return bodyUint, 0, nil + + case 429: + header := resp.Header + a := header["Retry-After"][0] + sec, err := strconv.ParseInt(a, 10, 0) + if err != nil { + return requestAccrual{}, 0, &errorsGM.ErrorGopherMart{errorsGM.StatusTooManyRequests, err} + } + return requestAccrual{}, sec, nil // + + case 500: + return requestAccrual{}, 0, errorsGM.ErrAccrualGetError // + } + + return requestAccrual{}, 0, errorsGM.ErrAccrualGetError +} diff --git a/internal/events/eventsJWT.go b/internal/events/eventsJWT.go new file mode 100644 index 000000000..143dab293 --- /dev/null +++ b/internal/events/eventsJWT.go @@ -0,0 +1,52 @@ +package events + +import ( + "time" + + "github.com/golang-jwt/jwt" + "github.com/pkg/errors" +) + +const Authorization = "Authorization" +const Bearer = "Bearer" +const keyJWT = "pqla3zxjonfgwouhf" + +type ClaimsUser struct { + Login string `json:"Login"` + jwt.StandardClaims +} + +func DecodeJWT(headertoken string) (Claims *ClaimsUser, err error) { + token, err := jwt.ParseWithClaims(headertoken, &ClaimsUser{}, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, errors.New("invalid signing method") + } + return []byte(keyJWT), nil + }) + if err != nil { + return nil, err + } + + claims, ok := token.Claims.(*ClaimsUser) + if !ok { + return nil, errors.New("token claims are not of type *tokenClaims") + } + + return claims, nil +} + +func EncodeJWT(login string) (token string, err error) { + userClaims := ClaimsUser{ + StandardClaims: jwt.StandardClaims{ + ExpiresAt: time.Now().Add(time.Minute * 1).Unix(), + IssuedAt: time.Now().Unix(), + }, + Login: login, + } + t := jwt.NewWithClaims(jwt.SigningMethodHS256, userClaims) + token, err = t.SignedString(keyJWT) + if err != nil { + return "", err + } + return token, nil +} diff --git a/internal/router/getAPIUserBalance.go b/internal/router/getAPIUserBalance.go new file mode 100644 index 000000000..672243ad5 --- /dev/null +++ b/internal/router/getAPIUserBalance.go @@ -0,0 +1,27 @@ +package router + +import ( + "encoding/json" + "net/http" + + "github.com/labstack/echo" +) + +func (s *serverMart) getAPIUserBalance(c echo.Context) error { + get := c.Get("user") + points, err := s.db.ReadUserPoints(get.(string)) + if err != nil { + c.Response().WriteHeader(http.StatusInternalServerError) + return nil + } + + allOrderJSON, err := json.Marshal(points) + if err != nil { + c.Response().WriteHeader(http.StatusInternalServerError) + return nil + } + + c.Response().WriteHeader(http.StatusOK) + c.Response().Write(allOrderJSON) + return nil +} diff --git a/internal/router/getAPIUserOrders.go b/internal/router/getAPIUserOrders.go new file mode 100644 index 000000000..f06a729e3 --- /dev/null +++ b/internal/router/getAPIUserOrders.go @@ -0,0 +1,31 @@ +package router + +import ( + "encoding/json" + "net/http" + + "github.com/labstack/echo" +) + +func (s *serverMart) getAPIUserOrders(c echo.Context) error { + get := c.Get("user") + allOrder, err := s.db.ReadAllOrderAccrualUser(get.(string)) + if err != nil { + c.Response().WriteHeader(http.StatusInternalServerError) + return nil + } + if len(allOrder) == 0 { + c.Response().WriteHeader(http.StatusNoContent) + return nil + } + + allOrderJSON, err := json.Marshal(allOrder) + if err != nil { + c.Response().WriteHeader(http.StatusInternalServerError) + return nil + } + + c.Response().WriteHeader(http.StatusOK) + c.Response().Write(allOrderJSON) + return nil +} diff --git a/internal/router/getAPIUserWithdrawals.go b/internal/router/getAPIUserWithdrawals.go new file mode 100644 index 000000000..49a9eaa4b --- /dev/null +++ b/internal/router/getAPIUserWithdrawals.go @@ -0,0 +1,31 @@ +package router + +import ( + "encoding/json" + "net/http" + + "github.com/labstack/echo" +) + +func (s *serverMart) getAPIUserWithdrawals(c echo.Context) error { + get := c.Get("user") + allOrder, err := s.db.ReadAllOrderWithdrawnUser(get.(string)) + if err != nil { + c.Response().WriteHeader(http.StatusInternalServerError) + return nil + } + if len(allOrder) == 0 { + c.Response().WriteHeader(http.StatusNoContent) + return nil + } + + allOrderJSON, err := json.Marshal(allOrder) + if err != nil { + c.Response().WriteHeader(http.StatusInternalServerError) + return nil + } + + c.Response().WriteHeader(http.StatusOK) + c.Response().Write(allOrderJSON) + return nil +} diff --git a/cmd/router/gzip.go b/internal/router/mwGzip.go similarity index 100% rename from cmd/router/gzip.go rename to internal/router/mwGzip.go diff --git a/internal/router/mwUserAuthentication.go b/internal/router/mwUserAuthentication.go new file mode 100644 index 000000000..2cce000f5 --- /dev/null +++ b/internal/router/mwUserAuthentication.go @@ -0,0 +1,39 @@ +package router + +import ( + "net/http" + "strings" + + "github.com/labstack/echo" + + "GopherMart/internal/events" +) + +func (s serverMart) mwUserAuthentication(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + headerAuth := c.Request().Header.Get(events.Authorization) + if headerAuth == "" { + c.Response().WriteHeader(http.StatusInternalServerError) + return nil + } + headerParts := strings.Split(headerAuth, " ") + if len(headerParts) != 2 || headerParts[0] != events.Bearer { + c.Response().WriteHeader(http.StatusInternalServerError) + return nil + } + + if len(headerParts[1]) == 0 { + c.Response().WriteHeader(http.StatusInternalServerError) + return nil + } + + claims, err := events.DecodeJWT(headerParts[1]) + if err != nil { + c.Response().WriteHeader(http.StatusInternalServerError) + return nil + } + + c.Set("user", claims.Login) + return next(c) + } +} diff --git a/internal/router/postAPIUserBalanceWithdraw.go b/internal/router/postAPIUserBalanceWithdraw.go new file mode 100644 index 000000000..a4573931d --- /dev/null +++ b/internal/router/postAPIUserBalanceWithdraw.go @@ -0,0 +1,58 @@ +package router + +import ( + "encoding/json" + "io" + "net/http" + "strconv" + + "github.com/labstack/echo" + "github.com/pkg/errors" + + "GopherMart/internal/errorsGM" + "GopherMart/internal/events" +) + +type orderWithdrawals struct { + Order string `json:"order"` + Sum float64 `json:"sum"` +} + +func (s *serverMart) postAPIUserBalanceWithdraw(c echo.Context) error { + defer c.Request().Body.Close() + body, err := io.ReadAll(c.Request().Body) + if err != nil { + c.Response().WriteHeader(http.StatusInternalServerError) + return nil + } + var bodyOrder orderWithdrawals + err = json.Unmarshal(body, &bodyOrder) + if err != nil { + c.Response().WriteHeader(http.StatusInternalServerError) + return nil + } + + atoi, err := strconv.Atoi(bodyOrder.Order) + if err != nil { + c.Response().WriteHeader(http.StatusUnprocessableEntity) + return nil + } + if !events.Valid(atoi) { + c.Response().WriteHeader(http.StatusUnprocessableEntity) + return nil + } + + get := c.Get("user") + err = s.db.WithdrawnUserPoints(get.(string), bodyOrder.Order, bodyOrder.Sum) + + if err != nil { + if errors.Is(err, errorsGM.ErrDontHavePoints) { + c.Response().WriteHeader(http.StatusPaymentRequired) + return nil + } + c.Response().WriteHeader(http.StatusInternalServerError) + return nil + } + c.Response().WriteHeader(http.StatusOK) + return nil +} diff --git a/internal/router/postAPIUserLogin.go b/internal/router/postAPIUserLogin.go new file mode 100644 index 000000000..5bfd8f831 --- /dev/null +++ b/internal/router/postAPIUserLogin.go @@ -0,0 +1,40 @@ +package router + +import ( + "encoding/json" + "io" + "net/http" + + "github.com/labstack/echo" + + "GopherMart/internal/events" +) + +func (s *serverMart) postAPIUserLogin(c echo.Context) error { + var userLog registration + defer c.Request().Body.Close() + body, err := io.ReadAll(c.Request().Body) + if err != nil { + c.Response().WriteHeader(http.StatusBadRequest) + return nil + } + if err = json.Unmarshal(body, &userLog); err != nil { + c.Response().WriteHeader(http.StatusBadRequest) + return nil + } + + tokenJWT, err := s.db.LoginUser(userLog.Login, userLog.Password) + if (tokenJWT == "") && (err == nil) { + c.Response().WriteHeader(http.StatusUnauthorized) + return nil + } + + if err != nil { + c.Response().WriteHeader(http.StatusInternalServerError) + return nil + } + + c.Response().Header().Set(events.Authorization, events.Bearer+" "+tokenJWT) + c.Response().WriteHeader(http.StatusOK) + return nil +} diff --git a/internal/router/postAPIUserOrders.go b/internal/router/postAPIUserOrders.go new file mode 100644 index 000000000..d54a4bf26 --- /dev/null +++ b/internal/router/postAPIUserOrders.go @@ -0,0 +1,47 @@ +package router + +import ( + "io" + "net/http" + "strconv" + + "github.com/labstack/echo" + "github.com/pkg/errors" + + "GopherMart/internal/errorsGM" + "GopherMart/internal/events" +) + +func (s *serverMart) postAPIUserOrders(c echo.Context) error { + defer c.Request().Body.Close() + body, err := io.ReadAll(c.Request().Body) + if err != nil { + c.Response().WriteHeader(http.StatusInternalServerError) + return nil + } + + bodyOrder := string(body) + atoi, err := strconv.Atoi(bodyOrder) + if err != nil { + c.Response().WriteHeader(http.StatusUnprocessableEntity) + return nil + } + if !events.Valid(atoi) { + c.Response().WriteHeader(http.StatusUnprocessableEntity) + return nil + } + + get := c.Get("user") + err = s.db.WriteOrderAccrual(bodyOrder, get.(string)) + if err != nil { + if errors.Is(err, errorsGM.ErrLoadedEarlierThisUser) { + c.Response().WriteHeader(http.StatusOK) + return nil + } + c.Response().WriteHeader(http.StatusInternalServerError) + return nil + } + + c.Response().WriteHeader(http.StatusAccepted) + return nil +} diff --git a/cmd/router/postAPIUserRegister.go b/internal/router/postAPIUserRegister.go similarity index 67% rename from cmd/router/postAPIUserRegister.go rename to internal/router/postAPIUserRegister.go index e61389c50..33eb65612 100644 --- a/cmd/router/postAPIUserRegister.go +++ b/internal/router/postAPIUserRegister.go @@ -9,29 +9,30 @@ import ( "github.com/jackc/pgx/v5/pgconn" "github.com/labstack/echo" "github.com/pkg/errors" + + "GopherMart/internal/events" ) -type register struct { +type registration struct { Login string `json:"login"` Password string `json:"password"` } -func (s *serverMart) postAPIUserRegister(c echo.Context) error { - var userLog register +func (s *serverMart) postAPIUserRegistration(c echo.Context) error { + var userLog registration defer c.Request().Body.Close() body, err := io.ReadAll(c.Request().Body) if err != nil { c.Response().WriteHeader(http.StatusBadRequest) - return nil //400 + return nil } if err = json.Unmarshal(body, &userLog); err != nil { c.Response().WriteHeader(http.StatusBadRequest) - return nil // 400 + return nil } - var pgErr *pgconn.PgError - hexCookie, err := s.db.RegisterUser(userLog.Login, userLog.Password) + tokenJWT, err := s.db.RegisterUser(userLog.Login, userLog.Password) if errors.As(err, &pgErr) { switch pgErr.Code { case pgerrcode.UniqueViolation: // дубликат @@ -42,13 +43,12 @@ func (s *serverMart) postAPIUserRegister(c echo.Context) error { return nil } } + if err != nil { + c.Response().WriteHeader(http.StatusInternalServerError) + return nil + } - cookie := new(http.Cookie) - cookie.Name = "GopherMart" - cookie.Value = hexCookie - cookie.Domain = userLog.Login - c.SetCookie(cookie) - + c.Response().Header().Set(events.Authorization, events.Bearer+" "+tokenJWT) c.Response().WriteHeader(http.StatusOK) return nil } diff --git a/cmd/router/router.go b/internal/router/router.go similarity index 63% rename from cmd/router/router.go rename to internal/router/router.go index 55d0b945e..84b2322d1 100644 --- a/cmd/router/router.go +++ b/internal/router/router.go @@ -6,7 +6,7 @@ import ( "github.com/caarlos0/env" "github.com/labstack/echo" - "GopherMart/cmd/events" + "GopherMart/internal/events" ) type Config struct { @@ -35,19 +35,21 @@ func (s serverMart) Router() error { e := echo.New() + go s.updateAccrual() + e.Use(s.gzip) - e.POST("/api/user/register", s.postAPIUserRegister) + e.POST("/api/user/registration", s.postAPIUserRegistration) e.POST("/api/user/login", s.postAPIUserLogin) - e.Use(s.CheakCookies) + e.Use(s.mwUserAuthentication) + + e.GET("/api/user/orders", s.getAPIUserOrders) // Получение списка загруженных заказов + e.GET("/api/user/balance", s.getAPIUserBalance) // Получение текущего баланса пользователя + e.GET("/api/user/withdrawals", s.getAPIUserWithdrawals) // Получение информации о выводе средств - //e.GET("/api/user/orders", s.getAPIUserOrders) - //e.GET("/api/user/balance", s.getAPIUserBalance) - //e.GET("/api/user/withdrawals", s.getAPIUserWithdrawals) - // - //e.POST("/api/user/orders", s.postAPIUserOrders) - //e.POST("/api/user/balance/withdraw", s.postAPIUserBalanceWithdraw) + e.POST("/api/user/orders", s.postAPIUserOrders) // Загрузка номера заказа + e.POST("/api/user/balance/withdraw", s.postAPIUserBalanceWithdraw) // Запрос на списание средств return nil } diff --git a/internal/router/update.go b/internal/router/update.go new file mode 100644 index 000000000..2d473dfa9 --- /dev/null +++ b/internal/router/update.go @@ -0,0 +1,49 @@ +package router + +import ( + "sync" + "time" + + "GopherMart/internal/events" +) + +func (s serverMart) updateAccrual() { + var wg, wgTimer sync.WaitGroup + for { + orders, err := s.db.ReadAllOrderAccrualNoComplite() + if err != nil { + time.Sleep(5 * time.Second) + continue + } + + if len(orders) != 0 { + for _, order := range orders { + wg.Add(1) + go s.worker(order.Order, order.Login, &wg, &wgTimer) + + } + } else { + time.Sleep(5 * time.Second) + } + + wg.Wait() + } + +} + +func (s serverMart) worker(order string, login string, wg, wgTimer *sync.WaitGroup) { + wgTimer.Wait() + accrual, sec, err := events.AccrualGet(s.cfg.AccrualAddress, order) + for sec != 0 { + wgTimer.Add(1) + time.Sleep(time.Duration(sec) * time.Second) + wgTimer.Done() + accrual, sec, err = events.AccrualGet(s.cfg.AccrualAddress, order) + } + + if err != nil { + return + } + _ = s.db.UpdateOrderAccrual(login, accrual) + wg.Done() +} From 20a3bdda211ea80305739f1c355b2520f87ca602 Mon Sep 17 00:00:00 2001 From: Demosfenys Date: Wed, 15 Feb 2023 18:41:38 +0300 Subject: [PATCH 6/6] =?UTF-8?q?=D0=9D=D0=B0=D0=B1=D1=80=D0=BE=D1=81=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=BE=D0=B1=D1=80=D0=B0=D1=89=D0=B5=D0=BD=D0=B8=D0=B9?= =?UTF-8?q?=20=D0=BA=20=D0=91=D0=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/gophermart.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gophermart.yml b/.github/workflows/gophermart.yml index 2a2d592e6..800415414 100644 --- a/.github/workflows/gophermart.yml +++ b/.github/workflows/gophermart.yml @@ -46,7 +46,7 @@ jobs: - name: Prepare binaries run: | - (cd cmd/gophermart && go build -o gophermart) + (cd cmd/gophermart && go build -buildvcs=false -o gophermart) (cd cmd/accrual && chmod +x accrual_linux_amd64) - name: Test