diff --git a/cmd/gophermart/main.go b/cmd/gophermart/main.go index fd71845..5f81e25 100644 --- a/cmd/gophermart/main.go +++ b/cmd/gophermart/main.go @@ -1,16 +1,27 @@ package main import ( + "context" "github.com/spqrcor/gofermart/internal/config" "github.com/spqrcor/gofermart/internal/db" "github.com/spqrcor/gofermart/internal/logger" "github.com/spqrcor/gofermart/internal/server" + "github.com/spqrcor/gofermart/internal/services" + "github.com/spqrcor/gofermart/internal/workers" ) func main() { + mainCtx := context.Background() config.Init() logger.Init() db.Init() - server.Start() + userService := services.NewUserService() + orderService := services.NewOrderService() + withdrawalService := services.NewWithdrawalService() + + orderWorker := workers.NewOrderWorker(mainCtx, orderService) + orderWorker.Run() + + server.Start(userService, orderService, withdrawalService) } diff --git a/internal/client/order.go b/internal/client/order.go new file mode 100644 index 0000000..e5dc66a --- /dev/null +++ b/internal/client/order.go @@ -0,0 +1,55 @@ +package client + +import ( + "bytes" + "encoding/json" + "errors" + "github.com/spqrcor/gofermart/internal/config" + "github.com/spqrcor/gofermart/internal/services" + "io" + "net/http" +) + +func SendOrder(OrderNum string) error { + url := config.Cfg.AccrualSystemAddress + "/api/orders" + resp, err := http.Post(url, "application/json", bytes.NewReader([]byte(`{"order":`+OrderNum+`,"goods":[{"description":"Чайник Bork","price":7000}]}`))) + if err != nil { + return err + } + if resp.StatusCode != http.StatusAccepted { + return errors.New("Error " + resp.Status) + } + return nil +} + +func CheckOrder(OrderNum string) (services.OrderFromAccrual, error) { + data := services.OrderFromAccrual{} + resp, err := http.Get(config.Cfg.AccrualSystemAddress + "/api/orders/" + OrderNum) + if err != nil { + return data, nil + } + if resp.StatusCode != http.StatusOK { + return data, errors.New("Error " + resp.Status) + } + + bodyBytes, _ := io.ReadAll(resp.Body) + defer func() { + _ = resp.Body.Close() + }() + if err = json.Unmarshal(bodyBytes, &data); err != nil { + return data, err + } + return data, nil +} + +func SetReward() error { + url := config.Cfg.AccrualSystemAddress + "/api/goods" + resp, err := http.Post(url, "application/json", bytes.NewReader([]byte(`{"match":"Bork","reward":10,"reward_type":"%"}`))) + if err != nil { + return err + } + if resp.StatusCode != http.StatusOK { + return errors.New("Error " + resp.Status) + } + return nil +} diff --git a/internal/config/config.go b/internal/config/config.go index f063d4a..92b559d 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -18,7 +18,7 @@ var Cfg = Config{ RunAddr: "localhost:8080", LogLevel: zap.InfoLevel, AccrualSystemAddress: "", - DatabaseURI: "postgres://postgres:Sp123456@localhost:5432/gofermart?sslmode=disable", + DatabaseURI: "", } func Init() { diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index 0002092..321123f 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -64,6 +64,8 @@ func AddOrdersHandler(o services.OrderRepository) http.HandlerFunc { return } + //_, _ = client.CheckOrder(orderNum) + err = o.Add(req.Context(), orderNum) if errors.Is(err, services.ErrOrderInvalidFormat) { http.Error(res, err.Error(), http.StatusUnprocessableEntity) @@ -79,6 +81,8 @@ func AddOrdersHandler(o services.OrderRepository) http.HandlerFunc { return } + //_ = client.SendOrder(orderNum) + res.WriteHeader(http.StatusAccepted) } } diff --git a/internal/server/server.go b/internal/server/server.go index e1ba66c..23fe866 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -12,17 +12,13 @@ import ( var publicRoutes = []string{"/api/user/register", "/api/user/login"} -func Start() { +func Start(userService services.UserRepository, orderService services.OrderRepository, withdrawalService services.WithdrawalRepository) { r := chi.NewRouter() r.Use(authenticateMiddleware) r.Use(loggerMiddleware) r.Use(middleware.Compress(5, "application/json", "text/html")) r.Use(getBodyMiddleware) - userService := services.NewUserService() - orderService := services.NewOrderService() - withdrawalService := services.NewWithdrawalService() - r.Post("/api/user/register", handlers.RegisterHandler(userService)) r.Post("/api/user/login", handlers.LoginHandler(userService)) r.Post("/api/user/orders", handlers.AddOrdersHandler(orderService)) @@ -34,6 +30,5 @@ func Start() { r.HandleFunc(`/*`, func(res http.ResponseWriter, req *http.Request) { res.WriteHeader(http.StatusBadRequest) }) - log.Fatal(http.ListenAndServe(config.Cfg.RunAddr, r)) } diff --git a/internal/services/order.go b/internal/services/order.go index a861086..43ff39c 100644 --- a/internal/services/order.go +++ b/internal/services/order.go @@ -19,6 +19,12 @@ type Order struct { UploadedAt string `json:"uploaded_at"` } +type OrderFromAccrual struct { + Order string `json:"order"` + Status string `json:"status"` + Accrual int `json:"accrual,omitempty"` +} + var ErrOrderAnotherUserExists = fmt.Errorf("order another user exists") var ErrOrderUserExists = fmt.Errorf("order user exists") var ErrOrderInvalidFormat = fmt.Errorf("order invalid format") @@ -27,6 +33,8 @@ var ErrOrdersNotFound = fmt.Errorf("orders not found") type OrderRepository interface { Add(ctx context.Context, orderNum string) error GetAll(ctx context.Context) ([]Order, error) + GetUnComplete(ctx context.Context) ([]string, error) + ChangeStatus(ctx context.Context, data OrderFromAccrual) error } type OrderService struct{} @@ -89,3 +97,59 @@ func (o *OrderService) GetAll(ctx context.Context) ([]Order, error) { } return orders, nil } + +func (o *OrderService) GetUnComplete(ctx context.Context) ([]string, error) { + var orders []string + + childCtx, cancel := context.WithTimeout(ctx, time.Second*5) + defer cancel() + + rows, err := db.Source.QueryContext(childCtx, "SELECT number FROM orders WHERE status IN ('NEW', 'PROCESSING') ORDER BY created_at") + if err != nil { + return nil, err + } + defer func() { + if err := rows.Close(); err != nil { + logger.Log.Error(err.Error()) + } + if err := rows.Err(); err != nil { + logger.Log.Error(err.Error()) + } + }() + + for rows.Next() { + var orderNum string + if err = rows.Scan(&orderNum); err != nil { + return nil, err + } + orders = append(orders, orderNum) + } + return orders, nil +} + +func (o *OrderService) ChangeStatus(ctx context.Context, data OrderFromAccrual) error { + childCtx, cancel := context.WithTimeout(ctx, time.Second*3) + defer cancel() + + tx, err := db.Source.BeginTx(childCtx, nil) + if err != nil { + return err + } + _, err = tx.ExecContext(childCtx, "UPDATE orders SET status = $1, accrual =$2 WHERE number = $3", data.Status, data.Accrual, data.Order) + if err != nil { + _ = tx.Rollback() + return err + } + if data.Accrual > 0 { + _, err = tx.ExecContext(childCtx, "UPDATE users SET balance = balance + $1 WHERE id = (SELECT user_id FROM orders WHERE number = $2)", data.Accrual, data.Order) + if err != nil { + _ = tx.Rollback() + return err + } + } + + if err = tx.Commit(); err != nil { + return err + } + return nil +} diff --git a/internal/workers/order.go b/internal/workers/order.go new file mode 100644 index 0000000..cc5c6ba --- /dev/null +++ b/internal/workers/order.go @@ -0,0 +1,57 @@ +package workers + +import ( + "context" + "github.com/spqrcor/gofermart/internal/client" + "github.com/spqrcor/gofermart/internal/logger" + "github.com/spqrcor/gofermart/internal/services" + "time" +) + +type OrderWorker struct { + ctx context.Context + orderService services.OrderRepository +} + +func NewOrderWorker(ctx context.Context, orderService services.OrderRepository) *OrderWorker { + return &OrderWorker{ctx: ctx, orderService: orderService} +} + +func (w *OrderWorker) Send(orderNum string) error { + if err := client.SendOrder(orderNum); err != nil { + return err + } + data := services.OrderFromAccrual{Order: orderNum, Status: "PROCESSING"} + if err := w.orderService.ChangeStatus(w.ctx, data); err != nil { + return err + } + return nil +} + +func (w *OrderWorker) Run() { + //_ = client.SetReward() + go w.doInterval() +} + +func (w *OrderWorker) doInterval() { + for range time.Tick(time.Second * 5) { + logger.Log.Info("execute 5s") + + orders, _ := w.orderService.GetUnComplete(w.ctx) + for _, order := range orders { + logger.Log.Info("before check") + result, err := client.CheckOrder(order) + if err != nil { + logger.Log.Info(err.Error()) + + } else { + err = w.orderService.ChangeStatus(w.ctx, result) + if err != nil { + logger.Log.Info(err.Error()) + + } + logger.Log.Info("after check") + } + } + } +}