Skip to content

Commit

Permalink
Добавил ручку POST user/orders
Browse files Browse the repository at this point in the history
  • Loading branch information
Froctnow committed May 19, 2024
1 parent 342dc36 commit 180782e
Show file tree
Hide file tree
Showing 22 changed files with 402 additions and 29 deletions.
8 changes: 8 additions & 0 deletions internal/app/client/pg/queries/gophermart.sql
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,11 @@ INSERT INTO gophermart.users (login, password) VALUES ($1, $2) ON CONFLICT (logi
{{define "GetUserForLogin"}}
SELECT id, login, password FROM gophermart.users WHERE login = $1;
{{end}}

{{define "CreateOrder"}}
INSERT INTO gophermart.orders (number, user_id) VALUES ($1, $2) ON CONFLICT (number) DO NOTHING RETURNING number;
{{end}}

{{define "CheckUserOrder"}}
SELECT EXISTS(SELECT 1 FROM gophermart.orders WHERE number = $1 AND user_id = $2);
{{end}}
35 changes: 35 additions & 0 deletions internal/app/gophermartprovider/check_user_order.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package gophermartprovider

import (
"context"
"fmt"

"github.com/Froctnow/yandex-go-diploma/pkg/pgclient"
)

func (p *GophermartDBProvider) CheckUserOrder(
ctx context.Context,
tx pgclient.Transaction,
orderNumber string,
userID string,
) (bool, error) {
rows, err := p.conn.NamedQueryxContext(
ctx,
"CheckUserOrder",
nil,
tx,
orderNumber,
userID,
)
if err != nil {
return false, fmt.Errorf("can't execute CheckUserOrder: %w", err)
}

err = rows.Err()

if err != nil {
return false, fmt.Errorf("can't execute CheckUserOrder: %w", err)
}

return pgclient.ValueFromRows[bool](rows)
}
36 changes: 36 additions & 0 deletions internal/app/gophermartprovider/create_order.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package gophermartprovider

import (
"context"
"fmt"

"github.com/Froctnow/yandex-go-diploma/pkg/pgclient"
)

// CreateOrder - создает новый заказ в базе данных и возвращает его номер
func (p *GophermartDBProvider) CreateOrder(
ctx context.Context,
tx pgclient.Transaction,
orderNumber string,
userID string,
) (string, error) {
rows, err := p.conn.NamedQueryxContext(
ctx,
"CreateOrder",
nil,
tx,
orderNumber,
userID,
)
if err != nil {
return "", fmt.Errorf("can't execute CreateOrder: %w", err)
}

err = rows.Err()

if err != nil {
return "", fmt.Errorf("can't execute CreateOrder: %w", err)
}

return pgclient.ValueFromRows[string](rows)
}
11 changes: 5 additions & 6 deletions internal/app/gophermartprovider/create_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,16 @@ import (
"context"
"fmt"

"github.com/Froctnow/yandex-go-diploma/internal/app/gophermartprovider/models"
"github.com/Froctnow/yandex-go-diploma/pkg/pgclient"
)

// CreateUser - создает нового пользователя в базе данных и возвращает его
// CreateUser - создает нового пользователя в базе данных и возвращает его ID
func (p *GophermartDBProvider) CreateUser(
ctx context.Context,
tx pgclient.Transaction,
login string,
password string,
) (models.User, error) {
) (string, error) {
rows, err := p.conn.NamedQueryxContext(
ctx,
"CreateUser",
Expand All @@ -24,14 +23,14 @@ func (p *GophermartDBProvider) CreateUser(
password,
)
if err != nil {
return models.User{}, fmt.Errorf("can't execute CreateUser: %w", err)
return "", fmt.Errorf("can't execute CreateUser: %w", err)
}

err = rows.Err()

if err != nil {
return models.User{}, fmt.Errorf("can't execute CreateUser: %w", err)
return "", fmt.Errorf("can't execute CreateUser: %w", err)
}

return pgclient.StructValueFromRows[models.User](rows)
return pgclient.ValueFromRows[string](rows)
}
8 changes: 6 additions & 2 deletions internal/app/gophermartprovider/gophermart_models.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,12 @@ type GophermartProvider interface {
RollbackTransaction(tx pgclient.Transaction, log logger.LogClient)
CommitTransaction(tx pgclient.Transaction) error

// CreateUser - создает нового пользователя в базе данных и возвращает его
CreateUser(ctx context.Context, tx pgclient.Transaction, login string, password string) (models.User, error)
// CreateUser - создает нового пользователя в базе данных и возвращает его ID
CreateUser(ctx context.Context, tx pgclient.Transaction, login string, password string) (string, error)
// GetUserForLogin - возвращает пользователя по логину и паролю
GetUserForLogin(ctx context.Context, tx pgclient.Transaction, login string) (models.User, error)
// CreateOrder - создает новый заказ в базе данных и возвращает его номер
CreateOrder(ctx context.Context, tx pgclient.Transaction, orderNumber string, userID string) (string, error)
// CheckUserOrder - проверят есть ли в базе номер заказа у пользователя
CheckUserOrder(ctx context.Context, tx pgclient.Transaction, orderNumber string, userID string) (bool, error)
}
8 changes: 8 additions & 0 deletions internal/app/gophermartprovider/models/order.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package models

type Order struct {
Number string `db:"number"`
Status string `db:"status"`
Accrual *uint32 `db:"accrual"`
UploadedAt string `db:"uploaded_at"`
}
28 changes: 26 additions & 2 deletions internal/app/httpserver/middleware/access_control.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,37 @@ type Claims struct {
UserID string `json:"user_id"`
}

type TokenIsInvalid struct {
}

func (e TokenIsInvalid) Error() string {
return "token is invalid"
}

func AccessControlMiddleware(cfg *config.Values, logger logger.LogClient) gin.HandlerFunc {
return func(c *gin.Context) {
jwtToken, err := c.Cookie("jwt")

if err != nil && !errors.Is(err, http.ErrNoCookie) {
if err != nil && errors.Is(err, http.ErrNoCookie) {
logger.WarnCtx(c, "jwt cookie is absent")
c.AbortWithStatus(http.StatusUnauthorized)
return
}

if err != nil {
logger.ErrorCtx(c, fmt.Errorf("can't get jwt token from cookie: %w", err))
c.AbortWithStatus(http.StatusInternalServerError)
return
}

decodedJwtToken, err := decodeJwtToken(jwtToken, cfg.JwtSecret)

if err != nil && errors.As(err, &TokenIsInvalid{}) {
logger.WarnCtx(c, "jwt token is invalid")
c.AbortWithStatus(http.StatusUnauthorized)
return
}

if err != nil {
logger.ErrorCtx(c, fmt.Errorf("can't decode jwt token: %w", err))
c.AbortWithStatus(http.StatusInternalServerError)
Expand All @@ -52,12 +71,17 @@ func decodeJwtToken(jwtToken string, jwtSecret string) (*Claims, error) {
token, err := jwt.ParseWithClaims(jwtToken, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return []byte(jwtSecret), nil
})

if err != nil && errors.Is(err, jwt.ErrSignatureInvalid) {
return nil, TokenIsInvalid{}
}

if err != nil {
return nil, fmt.Errorf("can't parse token: %w", err)
}

if !token.Valid {
return nil, fmt.Errorf("token is invalid")
return nil, TokenIsInvalid{}
}

return token.Claims.(*Claims), nil
Expand Down
4 changes: 3 additions & 1 deletion internal/app/httpserver/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"github.com/Froctnow/yandex-go-diploma/internal/app/httpserver/middleware"
"github.com/Froctnow/yandex-go-diploma/internal/app/httpserver/user"
"github.com/Froctnow/yandex-go-diploma/internal/app/usecase/auth"
"github.com/Froctnow/yandex-go-diploma/internal/app/usecase/order"
"github.com/Froctnow/yandex-go-diploma/internal/app/validator"
"github.com/Froctnow/yandex-go-diploma/pkg/logger"
"github.com/gin-gonic/gin"
Expand All @@ -23,6 +24,7 @@ func NewGophermartServer(
validator validator.Validator,
cfg *config.Values,
authUseCase auth.UseCase,
orderUseCase order.UseCase,
) GophermartServer {
ginEngine.Use(gin.Recovery())

Expand All @@ -32,6 +34,6 @@ func NewGophermartServer(
apiGroup.Use(middleware.CompressMiddleware())

return &gophermartServer{
user.NewRouter(apiGroup, authUseCase, validator, cfg, logger),
user.NewRouter(apiGroup, authUseCase, validator, cfg, logger, orderUseCase),
}
}
67 changes: 67 additions & 0 deletions internal/app/httpserver/user/create_order.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package user

import (
"fmt"
"io"
"net/http"
"strings"

"github.com/Froctnow/yandex-go-diploma/internal/app/httpserver/constants"
httpmodels "github.com/Froctnow/yandex-go-diploma/internal/app/httpserver/models"
ordererrors "github.com/Froctnow/yandex-go-diploma/internal/app/usecase/order/errors"
"github.com/gin-gonic/gin"

"github.com/pkg/errors"
)

func (r *userRouter) CreateOrder(ctx *gin.Context) {
headerContentType := ctx.GetHeader("Content-Type")
isCorrectHeaderContentType := checkHeaderContentType(headerContentType)

if !isCorrectHeaderContentType {
ctx.AbortWithStatusJSON(http.StatusBadRequest, httpmodels.ErrorResponse{Error: constants.MessageErrorIncorrectContentType})
return
}

body, err := io.ReadAll(ctx.Request.Body)

if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, httpmodels.ErrorResponse{Error: "Something went wrong"})
return
}

orderNumber := string(body)

errs := r.validator.UserCreateOrder(orderNumber)
if len(errs.Errors) != 0 {
ctx.AbortWithStatusJSON(http.StatusUnprocessableEntity, httpmodels.ErrorResponse{Error: errs.Error()})
return
}

userID := ctx.GetString(constants.ContextUserID)
err = r.orderUseCase.Create(ctx, orderNumber, userID)

if err != nil && errors.As(err, &ordererrors.UserOrderAlreadyExists{}) {
ctx.AbortWithStatus(http.StatusOK)
return
}

if err != nil && errors.As(err, &ordererrors.OrderAlreadyExists{}) {
ctx.AbortWithStatus(http.StatusConflict)
return
}

if err != nil {
r.logger.ErrorCtx(ctx, fmt.Errorf("failed create order"))
ctx.AbortWithStatusJSON(http.StatusInternalServerError, httpmodels.ErrorResponse{Error: err.Error()})
return
}

ctx.Status(http.StatusAccepted)
}

func checkHeaderContentType(value string) bool {
isTextPlain := strings.Contains(value, "text/plain")

return isTextPlain
}
4 changes: 2 additions & 2 deletions internal/app/httpserver/user/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func (r *userRouter) Login(ctx *gin.Context) {
return
}

err := r.authUseCase.Login(ctx, req.Login, req.Password)
userID, err := r.authUseCase.Login(ctx, req.Login, req.Password)

if err != nil && errors.As(err, &autherrors.IncorrectLoginPasswordError{}) {
ctx.AbortWithStatus(http.StatusUnauthorized)
Expand All @@ -46,7 +46,7 @@ func (r *userRouter) Login(ctx *gin.Context) {
return
}

err = r.authorizeUser(ctx, req.Login)
err = r.authorizeUser(ctx, userID)

if err != nil {
r.logger.ErrorCtx(ctx, fmt.Errorf("failed authorize user"))
Expand Down
22 changes: 14 additions & 8 deletions internal/app/httpserver/user/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import (

"github.com/Froctnow/yandex-go-diploma/internal/app/config"
"github.com/Froctnow/yandex-go-diploma/internal/app/httpserver/constants"
"github.com/Froctnow/yandex-go-diploma/internal/app/httpserver/middleware"
"github.com/Froctnow/yandex-go-diploma/internal/app/usecase/auth"
"github.com/Froctnow/yandex-go-diploma/internal/app/usecase/order"
"github.com/Froctnow/yandex-go-diploma/internal/app/validator"
"github.com/Froctnow/yandex-go-diploma/pkg/logger"
"github.com/gin-gonic/gin"
Expand All @@ -24,10 +26,11 @@ type Router interface {
}

type userRouter struct {
authUseCase auth.UseCase
validator validator.Validator
cfg *config.Values
logger logger.LogClient
authUseCase auth.UseCase
validator validator.Validator
cfg *config.Values
logger logger.LogClient
orderUseCase order.UseCase
}

func NewRouter(
Expand All @@ -36,17 +39,20 @@ func NewRouter(
validator validator.Validator,
cfg *config.Values,
logger logger.LogClient,
orderUseCase order.UseCase,
) Router {
router := &userRouter{
authUseCase: authUseCase,
validator: validator,
cfg: cfg,
logger: logger,
authUseCase: authUseCase,
validator: validator,
cfg: cfg,
logger: logger,
orderUseCase: orderUseCase,
}

userGroup := ginGroup.Group("/user")
userGroup.POST("/register", router.Register)
userGroup.POST("/login", router.Login)
userGroup.POST("/orders", middleware.AccessControlMiddleware(cfg, logger), router.CreateOrder)

return router
}
Expand Down
16 changes: 16 additions & 0 deletions internal/app/migration/migrations/0001__init_db.up.sql
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,19 @@ CREATE TABLE gophermart.users
password TEXT NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);

CREATE TYPE gophermart.order_status AS ENUM (
'NEW',
'PROCESSING',
'INVALID',
'PROCESSED'
);

CREATE TABLE gophermart.orders
(
number TEXT PRIMARY KEY,
user_id uuid NOT NULL REFERENCES gophermart.users (id),
accrual INTEGER,
status gophermart.order_status NOT NULL DEFAULT 'NEW'::gophermart.order_status,
uploaded_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
Loading

0 comments on commit 180782e

Please sign in to comment.