diff --git a/go.mod b/go.mod index fef5863..659bfcd 100644 --- a/go.mod +++ b/go.mod @@ -18,9 +18,9 @@ require ( go.uber.org/zap v1.27.0 google.golang.org/genproto/googleapis/api v0.0.0-20240221002015-b0ce06bbee7c google.golang.org/grpc v1.62.0 - google.golang.org/protobuf v1.32.0 + google.golang.org/protobuf v1.33.0 penahub.gitlab.yandexcloud.net/backend/penahub_common v0.0.0-20240223054633-6cb3d5ce45b6 - penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20240412164014-6ce70d76fedc + penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20240520145524-451212248881 ) require ( @@ -51,7 +51,7 @@ require ( github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.19.0 // indirect + golang.org/x/crypto v0.20.0 // indirect golang.org/x/net v0.21.0 // indirect golang.org/x/sync v0.6.0 // indirect golang.org/x/sys v0.17.0 // indirect diff --git a/go.sum b/go.sum index 46e61ab..ff9e388 100644 --- a/go.sum +++ b/go.sum @@ -144,8 +144,8 @@ golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg= +golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -249,8 +249,8 @@ google.golang.org/grpc v1.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk= google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= -google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -266,5 +266,5 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= penahub.gitlab.yandexcloud.net/backend/penahub_common v0.0.0-20240223054633-6cb3d5ce45b6 h1:oV+/HNX+JPoQ3/GUx08hio7d45WpY0AMGrFs7j70QlA= penahub.gitlab.yandexcloud.net/backend/penahub_common v0.0.0-20240223054633-6cb3d5ce45b6/go.mod h1:lTmpjry+8evVkXWbEC+WMOELcFkRD1lFMc7J09mOndM= -penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20240412164014-6ce70d76fedc h1:B9X8pOrqWPGbWZNXSJEUk/8GWeBDGQmMKgQ0F+PSliQ= -penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20240412164014-6ce70d76fedc/go.mod h1:/DcyAjBh41IbomuDu5QzhL9flZW6lWO3ZAEbUXKobk0= +penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20240520145524-451212248881 h1:U1/WGQdwZsmrV/ta7Uqm13Dg07IPN/5omS8gzBJYZv4= +penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20240520145524-451212248881/go.mod h1:oRyhT55ctjqp/7ZxIzkR7OsQ7T/NLibsfrbb7Ytns64= diff --git a/internal/app/app.go b/internal/app/app.go index ca27f53..cf332e2 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -81,6 +81,7 @@ func Run(config *models.Config, logger *zap.Logger) (appErr error) { VerificationURL: &config.Service.VerificationMicroservice.URL, TemplategenURL: &config.Service.TemplategenMicroserviceURL.URL, MailClient: &config.Service.Mail, + CodewordServiceHost: &config.Service.CodewordMicroservice, }) repositories := initialize.NewRepositories(initialize.RepositoriesDeps{ @@ -96,27 +97,38 @@ func Run(config *models.Config, logger *zap.Logger) (appErr error) { Brokers: brokers, }) - controllers := initialize.NewControllers(initialize.ControllersDeps{ + rpcControllers := initialize.NewRpcControllers(initialize.RpcControllersDeps{ Logger: logger, Services: services, }) encrypt := qutils.NewEncrypt(config.Service.PubKey, config.Service.PrivKey) + middleWare := http.NewMiddleWare(logger) - api := http.NewAPI2(logger, mongoDB, config, brokers.TariffConsumer, brokers.TariffProducer, encrypt) + httpControllers := initialize.NewHttpControllers(initialize.HttpControllersDeps{ + Logger: logger, + Encrypt: encrypt, + Producer: brokers.TariffProducer, + GRPC: &config.GRPC, + Repositories: repositories, + Clients: clients, + MiddleWare: middleWare, + }) serverHTTP := server.NewServer(server.ServerConfig{ Logger: logger, - Controllers: []server.Controller{api}, + Controllers: []server.Controller{httpControllers.CurrencyController, httpControllers.HistoryController, httpControllers.CartController, httpControllers.WalletController, httpControllers.AccountController}, JWTConfig: &config.Service.JWT, }) + serverHTTP.ListRoutes() + serverGRPC, grpcErr := server.NewGRPC(server.DepsGRPC{Logger: logger}) if grpcErr != nil { return grpcErr.Wrap("failed to init grpc server") } - serverGRPC.Register(controllers) + serverGRPC.Register(rpcControllers) go func() { if err := serverHTTP.Start(config.HTTP.Host + ":" + config.HTTP.Port); err != nil { diff --git a/internal/initialize/clients.go b/internal/initialize/clients.go index 42b6e35..b3de905 100644 --- a/internal/initialize/clients.go +++ b/internal/initialize/clients.go @@ -17,6 +17,7 @@ type ClientsDeps struct { VerificationURL *models.VerificationMicroserviceURL TemplategenURL *models.TemplategenMicroserviceURL MailClient *models.MailConfiguration + CodewordServiceHost *models.CodewordMicroserviceConfiguration } type Clients struct { @@ -28,6 +29,7 @@ type Clients struct { VerificationClient *client.VerificationClient TemplateClient *client.TemplateClient MailClient *client.MailClient + CodewordClient *client.CodewordClient } func NewClients(deps ClientsDeps) *Clients { @@ -73,5 +75,9 @@ func NewClients(deps ClientsDeps) *Clients { FiberClient: fiber.AcquireClient(), MailAddress: deps.MailClient.MailAddress, }), + CodewordClient: client.NewCodewordClient(client.CodewordClientDeps{ + Logger: deps.Logger, + CodewordServiceHost: deps.CodewordServiceHost.HostGRPC, + }), } } diff --git a/internal/initialize/controllers.go b/internal/initialize/controllers.go index 7d538cc..ec40739 100644 --- a/internal/initialize/controllers.go +++ b/internal/initialize/controllers.go @@ -2,22 +2,31 @@ package initialize import ( "go.uber.org/zap" + qutils "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/utils" + "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/interface/broker/tariff" "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/interface/controller/grpc/customer" "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/interface/controller/grpc/payment" + "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/interface/controller/http" + "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/interface/controller/http/account" + "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/interface/controller/http/cart" + "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/interface/controller/http/currency" + "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/interface/controller/http/history" + "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/interface/controller/http/wallet" + "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/models" ) -type ControllersDeps struct { +type RpcControllersDeps struct { Logger *zap.Logger Services *Services } -type Controllers struct { +type RpcControllers struct { PaymentController *payment.Controller CustomerController *customer.Controller } -func NewControllers(deps ControllersDeps) *Controllers { - return &Controllers{ +func NewRpcControllers(deps RpcControllersDeps) *RpcControllers { + return &RpcControllers{ PaymentController: payment.New(payment.Deps{ Logger: deps.Logger, PaymentCallbackService: deps.Services.PaymentCallbackService, @@ -28,3 +37,69 @@ func NewControllers(deps ControllersDeps) *Controllers { }), } } + +type HttpControllersDeps struct { + Encrypt *qutils.Encrypt + Producer *tariff.Producer + GRPC *models.ConfigurationGRPC + MiddleWare *http.MiddleWare + Logger *zap.Logger + Repositories *Repositories + Clients *Clients +} + +type HttpController struct { + AccountController *account.AccountController + CartController *cart.CartController + HistoryController *history.HistoryController + WalletController *wallet.WalletController + CurrencyController *currency.CurrencyController +} + +func NewHttpControllers(deps HttpControllersDeps) *HttpController { + return &HttpController{ + AccountController: account.NewAccountController(account.Deps{ + MiddleWare: deps.MiddleWare, + AccountRepo: deps.Repositories.AccountRepository, + Logger: deps.Logger, + Encrypt: deps.Encrypt, + AuthClient: deps.Clients.AuthClient, + }), + CartController: cart.NewCartController(cart.Deps{ + MiddleWare: deps.MiddleWare, + Logger: deps.Logger, + AccountRepo: deps.Repositories.AccountRepository, + HistoryRepo: deps.Repositories.HistoryRepository, + HubAdminClient: deps.Clients.HubadminClient, + DiscountClient: deps.Clients.DiscountClient, + CurrencyClient: deps.Clients.CurrencyClient, + Producer: deps.Producer, + }), + HistoryController: history.NewHistoryController(history.Deps{ + MiddleWare: deps.MiddleWare, + HistoryRepo: deps.Repositories.HistoryRepository, + AccountRepo: deps.Repositories.AccountRepository, + VerifyClient: deps.Clients.VerificationClient, + AuthClient: deps.Clients.AuthClient, + TemplateClient: deps.Clients.TemplateClient, + CodewordClient: deps.Clients.CodewordClient, + Logger: deps.Logger, + }), + WalletController: wallet.NewWalletController(wallet.Deps{ + MiddleWare: deps.MiddleWare, + AuthClient: deps.Clients.AuthClient, + PaymentClient: deps.Clients.PaymentClient, + GRPC: deps.GRPC, + AccountRepo: deps.Repositories.AccountRepository, + CurrencyClient: deps.Clients.CurrencyClient, + VerifyClient: deps.Clients.VerificationClient, + MailClient: deps.Clients.MailClient, + Logger: deps.Logger, + }), + CurrencyController: currency.NewCurrencyController(currency.Deps{ + CurrencyRepo: deps.Repositories.CurrencyRepository, + MiddleWare: deps.MiddleWare, + Logger: deps.Logger, + }), + } +} diff --git a/internal/interface/controller/http/account/controllers.go b/internal/interface/controller/http/account/controllers.go new file mode 100644 index 0000000..3453764 --- /dev/null +++ b/internal/interface/controller/http/account/controllers.go @@ -0,0 +1,215 @@ +package account + +import ( + "github.com/gofiber/fiber/v2" + "go.uber.org/zap" + "math" + qutils "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/utils" + "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/errors" + "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/interface/client" + "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/interface/controller/http" + "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/interface/repository" + "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/models" + "strconv" +) + +type Deps struct { + MiddleWare *http.MiddleWare + AccountRepo *repository.AccountRepository + Logger *zap.Logger + Encrypt *qutils.Encrypt + AuthClient *client.AuthClient +} + +type AccountController struct { + middleWare *http.MiddleWare + accountRepo *repository.AccountRepository + logger *zap.Logger + encrypt *qutils.Encrypt + authClient *client.AuthClient +} + +func NewAccountController(deps Deps) *AccountController { + return &AccountController{ + middleWare: deps.MiddleWare, + accountRepo: deps.AccountRepo, + logger: deps.Logger, + encrypt: deps.Encrypt, + authClient: deps.AuthClient, + } +} + +func (receiver *AccountController) DeleteAccount(ctx *fiber.Ctx) error { + userID, ok := receiver.middleWare.ExtractUserID(ctx) + if !ok || userID == "" { + return receiver.middleWare.NoAuth(ctx) + } + + account, err := receiver.accountRepo.Remove(ctx.Context(), userID) + if err != nil { + return receiver.middleWare.ErrorOld(ctx, err) + } + + return ctx.Status(fiber.StatusOK).JSON(account) +} + +func (receiver *AccountController) ChangeAccount(ctx *fiber.Ctx) error { + userID, ok := receiver.middleWare.ExtractUserID(ctx) + if !ok || userID == "" { + return receiver.middleWare.NoAuth(ctx) + } + + var request models.Name + if err := ctx.BodyParser(&request); err != nil { + return receiver.middleWare.Error(ctx, fiber.StatusBadRequest, "failed to bind json", err) + } + + account, err := receiver.accountRepo.UpdateName(ctx.Context(), userID, &request) + if err != nil { + return receiver.middleWare.ErrorOld(ctx, err) + } + + return ctx.Status(fiber.StatusOK).JSON(account) +} + +func (receiver *AccountController) SetAccountVerificationStatus(ctx *fiber.Ctx) error { + userID := ctx.Params("userId") + if userID == "" { + return receiver.middleWare.Error(ctx, fiber.StatusBadRequest, "invalid format for parameter userId") + } + + var request models.SetAccountStatus + if err := ctx.BodyParser(&request); err != nil { + return receiver.middleWare.Error(ctx, fiber.StatusBadRequest, "failed to bind json", err) + } + + account, err := receiver.accountRepo.SetStatus(ctx.Context(), userID, request.Status) + if err != nil { + receiver.logger.Error("failed to set status on of ", zap.Error(err)) + return receiver.middleWare.ErrorOld(ctx, err) + } + + return ctx.Status(fiber.StatusOK).JSON(account) +} + +func (receiver *AccountController) GetAccount(ctx *fiber.Ctx) error { + userID, ok := receiver.middleWare.ExtractUserID(ctx) + if !ok || userID == "" { + return receiver.middleWare.NoAuth(ctx) + } + + account, err := receiver.accountRepo.FindByUserID(ctx.Context(), userID) + if err != nil { + return receiver.middleWare.ErrorOld(ctx, err) + } + + return ctx.Status(fiber.StatusOK).JSON(account) +} + +func (receiver *AccountController) AddAccount(ctx *fiber.Ctx) error { + userID, ok := receiver.middleWare.ExtractUserID(ctx) + if !ok || userID == "" { + return receiver.middleWare.NoAuth(ctx) + } + + var er error + + quizFrom := ctx.Cookies("quizFrom") + quizUser := ctx.Cookies("quizUser") + if quizUser != "" { + quizUser, er = receiver.encrypt.DecryptStr([]byte(quizUser)) + if er != nil { + return receiver.middleWare.ErrorOld(ctx, er) + } + } + + account, err := receiver.accountRepo.FindByUserID(ctx.Context(), userID) + if err != nil && err.Type() != errors.ErrNotFound { + return receiver.middleWare.ErrorOld(ctx, err) + } + + if account != nil { + return receiver.middleWare.Error(ctx, fiber.StatusBadRequest, "account exists") + } + + user, err := receiver.authClient.GetUser(ctx.Context(), userID) + if err != nil { + return receiver.middleWare.ErrorOld(ctx, err) + } + + account, err = receiver.accountRepo.Insert(ctx.Context(), &models.Account{ + UserID: user.ID, Wallet: models.Wallet{Currency: models.DefaultCurrency}, From: quizFrom, Partner: quizUser}) + if err != nil { + return receiver.middleWare.ErrorOld(ctx, err) + } + + return ctx.Status(fiber.StatusOK).JSON(account) +} + +func (receiver *AccountController) DeleteDirectAccount(ctx *fiber.Ctx) error { + userID := ctx.Params("userId") + if userID == "" { + return receiver.middleWare.Error(ctx, fiber.StatusBadRequest, "invalid format for parameter userId") + } + + account, err := receiver.accountRepo.Remove(ctx.Context(), userID) + if err != nil { + return receiver.middleWare.ErrorOld(ctx, err) + } + + return ctx.Status(fiber.StatusOK).JSON(account) +} + +func (receiver *AccountController) GetDirectAccount(ctx *fiber.Ctx) error { + userID := ctx.Params("userId") + if userID == "" { + return receiver.middleWare.Error(ctx, fiber.StatusBadRequest, "invalid format for parameter userId") + } + + account, err := receiver.accountRepo.FindByUserID(ctx.Context(), userID) + if err != nil { + return receiver.middleWare.ErrorOld(ctx, err) + } + + return ctx.Status(fiber.StatusOK).JSON(account) +} + +func (receiver *AccountController) PaginationAccounts(ctx *fiber.Ctx) error { + pageStr := ctx.Query("page", "1") + limitStr := ctx.Query("limit", "100") + + page, err := strconv.ParseInt(pageStr, 10, 64) + if err != nil || page < 1 { + page = 1 + } + limit, err := strconv.ParseInt(limitStr, 10, 64) + if err != nil || limit < 1 { + limit = models.DefaultLimit + } else { + limit = int64(math.Min(float64(limit), float64(models.DefaultLimit))) + } + + count, err := receiver.accountRepo.CountAll(ctx.Context()) + if err != nil { + return receiver.middleWare.ErrorOld(ctx, err) + } + + if count == 0 { + response := models.PaginationResponse[models.Account]{TotalPages: 0, Records: []models.Account{}} + return ctx.Status(fiber.StatusOK).JSON(response) + } + + totalPages := int64(math.Ceil(float64(count) / float64(limit))) + + accounts, err := receiver.accountRepo.FindMany(ctx.Context(), page, limit) + if err != nil { + return receiver.middleWare.ErrorOld(ctx, err) + } + + response := models.PaginationResponse[models.Account]{ + TotalPages: totalPages, + Records: accounts, + } + + return ctx.Status(fiber.StatusOK).JSON(response) +} diff --git a/internal/interface/controller/http/account/route.go b/internal/interface/controller/http/account/route.go new file mode 100644 index 0000000..5a4bfec --- /dev/null +++ b/internal/interface/controller/http/account/route.go @@ -0,0 +1,18 @@ +package account + +import "github.com/gofiber/fiber/v2" + +func (receiver *AccountController) Register(router fiber.Router) { + router.Delete("/account", receiver.DeleteAccount) + router.Get("/account", receiver.GetAccount) + router.Patch("/account", receiver.ChangeAccount) + router.Post("/account", receiver.AddAccount) + router.Delete("/account/:userId", receiver.DeleteDirectAccount) + router.Get("/account/:userId", receiver.GetDirectAccount) + router.Patch("/account/:userId", receiver.SetAccountVerificationStatus) + router.Get("/accounts", receiver.PaginationAccounts) +} + +func (receiver *AccountController) Name() string { + return "" +} diff --git a/internal/interface/controller/http/cart/controllers.go b/internal/interface/controller/http/cart/controllers.go new file mode 100644 index 0000000..018def2 --- /dev/null +++ b/internal/interface/controller/http/cart/controllers.go @@ -0,0 +1,273 @@ +package cart + +import ( + "github.com/gofiber/fiber/v2" + "go.uber.org/zap" + "google.golang.org/protobuf/types/known/timestamppb" + "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/errors" + "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/interface/broker/tariff" + "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/interface/client" + "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/interface/controller/http" + "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/interface/repository" + "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/models" + "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/proto/discount" + "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/utils" + "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/utils/transfer" + "penahub.gitlab.yandexcloud.net/pena-services/customer/pkg/validate" + "sync" + "time" +) + +type Deps struct { + MiddleWare *http.MiddleWare + Logger *zap.Logger + AccountRepo *repository.AccountRepository + HistoryRepo *repository.HistoryRepository + HubAdminClient *client.HubadminClient + DiscountClient *client.DiscountClient + CurrencyClient *client.CurrencyClient + Producer *tariff.Producer +} + +type CartController struct { + middleWare *http.MiddleWare + logger *zap.Logger + accountRepo *repository.AccountRepository + historyRepo *repository.HistoryRepository + hubAdminClient *client.HubadminClient + discountClient *client.DiscountClient + currencyClient *client.CurrencyClient + producer *tariff.Producer +} + +func NewCartController(deps Deps) *CartController { + return &CartController{ + middleWare: deps.MiddleWare, + logger: deps.Logger, + accountRepo: deps.AccountRepo, + historyRepo: deps.HistoryRepo, + hubAdminClient: deps.HubAdminClient, + discountClient: deps.DiscountClient, + currencyClient: deps.CurrencyClient, + producer: deps.Producer, + } +} + +func (receiver *CartController) RemoveFromCart(ctx *fiber.Ctx) error { + userID, ok := receiver.middleWare.ExtractUserID(ctx) + if !ok || userID == "" { + return receiver.middleWare.NoAuth(ctx) + } + + id := ctx.Query("id") + if id == "" { + return receiver.middleWare.Error(ctx, fiber.StatusBadRequest, "empty item id") + } + + cartItems, err := receiver.accountRepo.RemoveItemFromCart(ctx.Context(), userID, id) + if err != nil { + return receiver.middleWare.ErrorOld(ctx, err) + } + + return ctx.Status(fiber.StatusOK).JSON(cartItems) +} + +func (receiver *CartController) Add2cart(ctx *fiber.Ctx) error { + userID, ok := receiver.middleWare.ExtractUserID(ctx) + if !ok || userID == "" { + return receiver.middleWare.NoAuth(ctx) + } + + token, ok := receiver.middleWare.ExtractToken(ctx) + if !ok || token == "" { + return receiver.middleWare.NoAuth(ctx) + } + + tariffID := ctx.Query("id") + if tariffID == "" { + return receiver.middleWare.Error(ctx, fiber.StatusBadRequest, "empty item id") + } + + tariff, err := receiver.hubAdminClient.GetTariff(ctx.Context(), token, tariffID) + if err != nil { + return receiver.middleWare.ErrorOld(ctx, err) + } + + if tariff == nil { + return receiver.middleWare.Error(ctx, fiber.StatusNotFound, "tariff not found") + } + + cartItems, err := receiver.accountRepo.AddItemToCart(ctx.Context(), userID, tariffID) + if err != nil { + return receiver.middleWare.ErrorOld(ctx, err) + } + + return ctx.Status(fiber.StatusOK).JSON(cartItems) +} + +func (receiver *CartController) PayCart(ctx *fiber.Ctx) error { + userID, ok := receiver.middleWare.ExtractUserID(ctx) + if !ok || userID == "" { + return receiver.middleWare.NoAuth(ctx) + } + + accessToken, ok := receiver.middleWare.ExtractToken(ctx) + if !ok || accessToken == "" { + return receiver.middleWare.NoAuth(ctx) + } + + account, err := receiver.accountRepo.FindByUserID(ctx.Context(), userID) + if err != nil { + return receiver.middleWare.ErrorOld(ctx, err) + } + + receiver.logger.Info("account for pay", zap.Any("acc", account)) + + tariffs, err := receiver.hubAdminClient.GetTariffs(ctx.Context(), accessToken, account.Cart) + if err != nil { + return receiver.middleWare.ErrorOld(ctx, err) + } + + receiver.logger.Info("tariffs for pay", zap.Any("acc", tariffs)) + + tariffsAmount := utils.CalculateCartPurchasesAmount(tariffs) + + discountResponse, err := receiver.discountClient.Apply(ctx.Context(), &discount.ApplyDiscountRequest{ + UserInformation: &discount.UserInformation{ + ID: account.UserID, + Type: string(account.Status), + PurchasesAmount: uint64(account.Wallet.Spent), + CartPurchasesAmount: tariffsAmount, + }, + Products: transfer.TariffsToProductInformations(tariffs), + Date: timestamppb.New(time.Now()), + }) + if err != nil { + return receiver.middleWare.ErrorOld(ctx, err) + } + + receiver.logger.Info("discountResponse for pay", zap.Any("acc", discount.ApplyDiscountRequest{ + UserInformation: &discount.UserInformation{ + ID: account.UserID, + Type: string(account.Status), + PurchasesAmount: uint64(account.Wallet.Spent), + CartPurchasesAmount: tariffsAmount, + }, + Products: transfer.TariffsToProductInformations(tariffs), + Date: timestamppb.New(time.Now()), + })) + + if account.Wallet.Money < int64(discountResponse.Price) { + return receiver.middleWare.Error(ctx, fiber.StatusPaymentRequired, "insufficient funds: %d", int64(discountResponse.Price)-account.Wallet.Money) + } + + for _, applied := range discountResponse.AppliedDiscounts { + if applied.Condition.User != nil && *applied.Condition.User != "" { + _, err := receiver.discountClient.DeleteDiscount(ctx.Context(), &discount.GetDiscountByIDRequest{ + ID: applied.ID, + }) + + if err != nil { + return receiver.middleWare.Error(ctx, fiber.StatusInternalServerError, "failed delete discount by id:%s", applied.ID) + } + } + } + + // WithdrawAccountWalletMoney + + request := models.WithdrawAccountWallet{ + Money: int64(discountResponse.Price), + Account: account, + } + + if validate.IsStringEmpty(request.Account.Wallet.Currency) { + request.Account.Wallet.Currency = models.InternalCurrencyKey + } + + var updatedAccount *models.Account + + if request.Account.Wallet.Currency == models.InternalCurrencyKey { + accountx, err := receiver.accountRepo.ChangeWallet(ctx.Context(), request.Account.UserID, &models.Wallet{ + Cash: request.Account.Wallet.Cash - request.Money, + Money: request.Account.Wallet.Money - request.Money, + Spent: request.Account.Wallet.Spent + request.Money, + PurchasesAmount: request.Account.Wallet.PurchasesAmount, + Currency: request.Account.Wallet.Currency, + }) + if err != nil { + return receiver.middleWare.ErrorOld(ctx, err) + } + updatedAccount = accountx + } else { + cash, err := receiver.currencyClient.Translate(ctx.Context(), &models.TranslateCurrency{ + Money: request.Money, + From: models.InternalCurrencyKey, + To: request.Account.Wallet.Currency, + }) + if err != nil { + return receiver.middleWare.ErrorOld(ctx, err) + } + + accountx, err := receiver.accountRepo.ChangeWallet(ctx.Context(), request.Account.UserID, &models.Wallet{ + Cash: request.Account.Wallet.Cash - cash, + Money: request.Account.Wallet.Money - request.Money, + Spent: request.Account.Wallet.Spent + request.Money, + PurchasesAmount: request.Account.Wallet.PurchasesAmount, + Currency: request.Account.Wallet.Currency, + }) + if err != nil { + return receiver.middleWare.ErrorOld(ctx, err) + } + + updatedAccount = accountx + } + + if _, err := receiver.historyRepo.Insert(ctx.Context(), &models.History{ + Key: models.CustomerHistoryKeyPayCart, + UserID: account.UserID, + Comment: "Успешная оплата корзины", + RawDetails: models.RawDetails{ + Tariffs: tariffs, + Price: int64(discountResponse.Price), + }, + }); err != nil { + return receiver.middleWare.ErrorOld(ctx, err) + } + + // TODO: обработать ошибки при отправке сообщений + + sendErrors := make([]errors.Error, 0) + waitGroup := sync.WaitGroup{} + mutex := sync.Mutex{} + + for _, tariff := range tariffs { + waitGroup.Add(1) + + go func(currentTariff models.Tariff) { + defer waitGroup.Done() + + if err := receiver.producer.Send(ctx.Context(), userID, ¤tTariff); err != nil { + mutex.Lock() + defer mutex.Unlock() + sendErrors = append(sendErrors, err) + } + }(tariff) + } + + waitGroup.Wait() + + if len(sendErrors) > 0 { + for _, err := range sendErrors { + receiver.logger.Error("failed to send tariffs to broker on of ", zap.Error(err)) + } + return receiver.middleWare.Error(ctx, fiber.StatusInternalServerError, "failed to send tariffs to broker") + } + + if _, err := receiver.accountRepo.ClearCart(ctx.Context(), account.UserID); err != nil { + return receiver.middleWare.ErrorOld(ctx, err) + } + + updatedAccount.Cart = []string{} + + return ctx.Status(fiber.StatusOK).JSON(updatedAccount) +} diff --git a/internal/interface/controller/http/cart/route.go b/internal/interface/controller/http/cart/route.go new file mode 100644 index 0000000..cd893bc --- /dev/null +++ b/internal/interface/controller/http/cart/route.go @@ -0,0 +1,13 @@ +package cart + +import "github.com/gofiber/fiber/v2" + +func (receiver *CartController) Register(router fiber.Router) { + router.Delete("/cart", receiver.RemoveFromCart) + router.Patch("/cart", receiver.Add2cart) + router.Post("/cart/pay", receiver.PayCart) +} + +func (receiver *CartController) Name() string { + return "" +} diff --git a/internal/interface/controller/http/controllers.go b/internal/interface/controller/http/controllers.go deleted file mode 100644 index a3c9ed4..0000000 --- a/internal/interface/controller/http/controllers.go +++ /dev/null @@ -1,1025 +0,0 @@ -package http - -import ( - "fmt" - "github.com/gofiber/fiber/v2" - "math" - "os" - codeword_rpc "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/proto/codeword" - "strconv" - "sync" - "time" - - "go.mongodb.org/mongo-driver/mongo" - "go.uber.org/zap" - "google.golang.org/protobuf/types/known/timestamppb" - qutils "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/utils" - "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/errors" - "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/interface/broker/tariff" - "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/interface/client" - "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/interface/repository" - "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/models" - "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/proto/discount" - "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/service/history" - "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/utils" - "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/utils/transfer" - "penahub.gitlab.yandexcloud.net/pena-services/customer/pkg/validate" -) - -const defaultCurrency = "RUB" // TODO move - -type API2 struct { - logger *zap.Logger - history repository.HistoryRepository - account repository.AccountRepository - currency repository.CurrencyRepository - producer *tariff.Producer - consumer *tariff.Consumer - clients clients - grpc models.ConfigurationGRPC - encrypt *qutils.Encrypt -} - -type clients struct { - auth *client.AuthClient - hubadmin *client.HubadminClient - currency *client.CurrencyClient - discount *client.DiscountClient - payment *client.PaymentClient - verify *client.VerificationClient - template *client.TemplateClient - mail *client.MailClient - codeword *client.CodewordClient -} - -func NewAPI2(logger *zap.Logger, db *mongo.Database, config *models.Config, consumer *tariff.Consumer, producer *tariff.Producer, encrypt *qutils.Encrypt) *API2 { - return &API2{ - logger: logger, - history: repository.NewHistoryRepository2(logger, db.Collection("histories")), - currency: repository.NewCurrencyRepository2(logger, db.Collection("currency_lists")), - account: repository.NewAccountRepository2(logger, db.Collection("accounts")), - consumer: consumer, - producer: producer, - encrypt: encrypt, - grpc: config.GRPC, - clients: clients{ - auth: client.NewAuthClient(client.AuthClientDeps{Logger: logger, URLs: &config.Service.AuthMicroservice.URL}), - hubadmin: client.NewHubadminClient(client.HubadminClientDeps{Logger: logger, URLs: &config.Service.HubadminMicroservice.URL}), - currency: client.NewCurrencyClient(client.CurrencyClientDeps{Logger: logger, URLs: &config.Service.CurrencyMicroservice.URL}), - discount: client.NewDiscountClient(client.DiscountClientDeps{Logger: logger, DiscountServiceHost: config.Service.DiscountMicroservice.HostGRPC}), - payment: client.NewPaymentClient(client.PaymentClientDeps{Logger: logger, PaymentServiceHost: config.Service.PaymentMicroservice.HostGRPC}), - verify: client.NewVerificationClient(client.VerificationClientDeps{Logger: logger, URLs: &config.Service.VerificationMicroservice.URL}), - template: client.NewTemplateClient(client.TemplateClientDeps{Logger: logger, URLs: &config.Service.TemplategenMicroserviceURL.URL}), - mail: client.NewMailClient(client.MailClientDeps{Logger: logger, - ApiUrl: config.Service.Mail.ApiUrl, - Sender: config.Service.Mail.Sender, - Auth: &models.PlainAuth{ - Identity: config.Service.Mail.Auth.Identity, - Username: config.Service.Mail.Auth.Username, - Password: config.Service.Mail.Auth.Password, - }, - ApiKey: config.Service.Mail.ApiKey, - FiberClient: fiber.AcquireClient(), - MailAddress: config.Service.Mail.MailAddress, - }), - codeword: client.NewCodewordClient(client.CodewordClientDeps{Logger: logger, CodewordServiceHost: config.Service.CodewordMicroservice.HostGRPC}), - }, - } -} - -func (api *API2) error(ctx *fiber.Ctx, status int, message string, rest ...any) error { - if len(rest) > 0 { - message = fmt.Sprintf(message, rest...) - } - api.logger.Error(message) - return ctx.Status(status).JSON(models.ResponseErrorHTTP{ - StatusCode: status, - Message: message, - }) -} - -func (api *API2) errorOld(ctx *fiber.Ctx, err error) error { - api.logger.Error("error:", zap.Error(err)) - return ctx.Status(fiber.StatusInternalServerError).JSON(models.ResponseErrorHTTP{ - StatusCode: fiber.StatusInternalServerError, - Message: err.Error(), - }) -} - -func (api *API2) noauth(ctx *fiber.Ctx) error { - return api.error(ctx, fiber.StatusUnauthorized, "failed to get jwt payload") -} - -func (api *API2) extractUserID(ctx *fiber.Ctx) (string, bool) { - id, ok := ctx.Context().UserValue(models.AuthJWTDecodedUserIDKey).(string) - return id, ok -} - -func (api *API2) extractToken(ctx *fiber.Ctx) (string, bool) { - token, ok := ctx.Context().UserValue(models.AuthJWTDecodedAccessTokenKey).(string) - return token, ok -} - -// Health - -func (api *API2) GetHealth(ctx *fiber.Ctx) error { - return ctx.Status(fiber.StatusOK).SendString("OK") -} - -// Account - -func (api *API2) DeleteAccount(ctx *fiber.Ctx) error { - userID, ok := api.extractUserID(ctx) - if !ok || userID == "" { - return api.noauth(ctx) - } - - account, err := api.account.Remove(ctx.Context(), userID) - if err != nil { - return api.errorOld(ctx, err) - } - - return ctx.Status(fiber.StatusOK).JSON(account) -} - -func (api *API2) ChangeAccount(ctx *fiber.Ctx) error { - userID, ok := api.extractUserID(ctx) - if !ok || userID == "" { - return api.noauth(ctx) - } - - var request models.Name - if err := ctx.BodyParser(&request); err != nil { - return api.error(ctx, fiber.StatusBadRequest, "failed to bind json", err) - } - - account, err := api.account.UpdateName(ctx.Context(), userID, &request) - if err != nil { - return api.errorOld(ctx, err) - } - - return ctx.Status(fiber.StatusOK).JSON(account) -} - -func (api *API2) SetAccountVerificationStatus(ctx *fiber.Ctx) error { - userID := ctx.Params("userId") - if userID == "" { - return api.error(ctx, fiber.StatusBadRequest, "invalid format for parameter userId") - } - - var request models.SetAccountStatus - if err := ctx.BodyParser(&request); err != nil { - return api.error(ctx, fiber.StatusBadRequest, "failed to bind json", err) - } - - account, err := api.account.SetStatus(ctx.Context(), userID, request.Status) - if err != nil { - api.logger.Error("failed to set status on of ", zap.Error(err)) - return api.errorOld(ctx, err) - } - - return ctx.Status(fiber.StatusOK).JSON(account) -} - -func (api *API2) GetAccount(ctx *fiber.Ctx) error { - userID, ok := api.extractUserID(ctx) - if !ok || userID == "" { - return api.noauth(ctx) - } - - account, err := api.account.FindByUserID(ctx.Context(), userID) - if err != nil { - return api.errorOld(ctx, err) - } - - return ctx.Status(fiber.StatusOK).JSON(account) -} - -func (api *API2) AddAccount(ctx *fiber.Ctx) error { - userID, ok := api.extractUserID(ctx) - if !ok || userID == "" { - return api.noauth(ctx) - } - - var er error - - quizFrom := ctx.Cookies("quizFrom") - quizUser := ctx.Cookies("quizUser") - if quizUser != "" { - quizUser, er = api.encrypt.DecryptStr([]byte(quizUser)) - if er != nil { - return api.errorOld(ctx, er) - } - } - - account, err := api.account.FindByUserID(ctx.Context(), userID) - if err != nil && err.Type() != errors.ErrNotFound { - return api.errorOld(ctx, err) - } - - if account != nil { - return api.error(ctx, fiber.StatusBadRequest, "account exists") - } - - user, err := api.clients.auth.GetUser(ctx.Context(), userID) - if err != nil { - return api.errorOld(ctx, err) - } - - account, err = api.account.Insert(ctx.Context(), &models.Account{ - UserID: user.ID, Wallet: models.Wallet{Currency: defaultCurrency}, From: quizFrom, Partner: quizUser}) - if err != nil { - return api.errorOld(ctx, err) - } - - return ctx.Status(fiber.StatusOK).JSON(account) -} - -func (api *API2) DeleteDirectAccount(ctx *fiber.Ctx) error { - userID := ctx.Params("userId") - if userID == "" { - return api.error(ctx, fiber.StatusBadRequest, "invalid format for parameter userId") - } - - account, err := api.account.Remove(ctx.Context(), userID) - if err != nil { - return api.errorOld(ctx, err) - } - - return ctx.Status(fiber.StatusOK).JSON(account) -} - -func (api *API2) GetDirectAccount(ctx *fiber.Ctx) error { - userID := ctx.Params("userId") - if userID == "" { - return api.error(ctx, fiber.StatusBadRequest, "invalid format for parameter userId") - } - - account, err := api.account.FindByUserID(ctx.Context(), userID) - if err != nil { - return api.errorOld(ctx, err) - } - - return ctx.Status(fiber.StatusOK).JSON(account) -} - -func (api *API2) PaginationAccounts(ctx *fiber.Ctx) error { - pageStr := ctx.Query("page", "1") - limitStr := ctx.Query("limit", "100") - - page, err := strconv.ParseInt(pageStr, 10, 64) - if err != nil || page < 1 { - page = 1 - } - limit, err := strconv.ParseInt(limitStr, 10, 64) - if err != nil || limit < 1 { - limit = models.DefaultLimit - } else { - limit = int64(math.Min(float64(limit), float64(models.DefaultLimit))) - } - - count, err := api.account.CountAll(ctx.Context()) - if err != nil { - return api.errorOld(ctx, err) - } - - if count == 0 { - response := models.PaginationResponse[models.Account]{TotalPages: 0, Records: []models.Account{}} - return ctx.Status(fiber.StatusOK).JSON(response) - } - - totalPages := int64(math.Ceil(float64(count) / float64(limit))) - - accounts, err := api.account.FindMany(ctx.Context(), page, limit) - if err != nil { - return api.errorOld(ctx, err) - } - - response := models.PaginationResponse[models.Account]{ - TotalPages: totalPages, - Records: accounts, - } - - return ctx.Status(fiber.StatusOK).JSON(response) -} - -// Cart - -func (api *API2) RemoveFromCart(ctx *fiber.Ctx) error { - userID, ok := api.extractUserID(ctx) - if !ok || userID == "" { - return api.noauth(ctx) - } - - id := ctx.Query("id") - if id == "" { - return api.error(ctx, fiber.StatusBadRequest, "empty item id") - } - - cartItems, err := api.account.RemoveItemFromCart(ctx.Context(), userID, id) - if err != nil { - return api.errorOld(ctx, err) - } - - return ctx.Status(fiber.StatusOK).JSON(cartItems) -} - -func (api *API2) Add2cart(ctx *fiber.Ctx) error { - userID, ok := api.extractUserID(ctx) - if !ok || userID == "" { - return api.noauth(ctx) - } - - token, ok := api.extractToken(ctx) - if !ok || token == "" { - return api.noauth(ctx) - } - - tariffID := ctx.Query("id") - if tariffID == "" { - return api.error(ctx, fiber.StatusBadRequest, "empty item id") - } - - tariff, err := api.clients.hubadmin.GetTariff(ctx.Context(), token, tariffID) - if err != nil { - return api.errorOld(ctx, err) - } - - if tariff == nil { - return api.error(ctx, fiber.StatusNotFound, "tariff not found") - } - - cartItems, err := api.account.AddItemToCart(ctx.Context(), userID, tariffID) - if err != nil { - return api.errorOld(ctx, err) - } - - return ctx.Status(fiber.StatusOK).JSON(cartItems) -} - -func (api *API2) PayCart(ctx *fiber.Ctx) error { - userID, ok := api.extractUserID(ctx) - if !ok || userID == "" { - return api.noauth(ctx) - } - - accessToken, ok := api.extractToken(ctx) - if !ok || accessToken == "" { - return api.noauth(ctx) - } - - account, err := api.account.FindByUserID(ctx.Context(), userID) - if err != nil { - return api.errorOld(ctx, err) - } - - api.logger.Info("account for pay", zap.Any("acc", account)) - - tariffs, err := api.clients.hubadmin.GetTariffs(ctx.Context(), accessToken, account.Cart) - if err != nil { - return api.errorOld(ctx, err) - } - - api.logger.Info("tariffs for pay", zap.Any("acc", tariffs)) - - tariffsAmount := utils.CalculateCartPurchasesAmount(tariffs) - - discountResponse, err := api.clients.discount.Apply(ctx.Context(), &discount.ApplyDiscountRequest{ - UserInformation: &discount.UserInformation{ - ID: account.UserID, - Type: string(account.Status), - PurchasesAmount: uint64(account.Wallet.Spent), - CartPurchasesAmount: tariffsAmount, - }, - Products: transfer.TariffsToProductInformations(tariffs), - Date: timestamppb.New(time.Now()), - }) - if err != nil { - return api.errorOld(ctx, err) - } - - api.logger.Info("discountResponse for pay", zap.Any("acc", discount.ApplyDiscountRequest{ - UserInformation: &discount.UserInformation{ - ID: account.UserID, - Type: string(account.Status), - PurchasesAmount: uint64(account.Wallet.Spent), - CartPurchasesAmount: tariffsAmount, - }, - Products: transfer.TariffsToProductInformations(tariffs), - Date: timestamppb.New(time.Now()), - })) - - if account.Wallet.Money < int64(discountResponse.Price) { - return api.error(ctx, fiber.StatusPaymentRequired, "insufficient funds: %d", int64(discountResponse.Price)-account.Wallet.Money) - } - - for _, applied := range discountResponse.AppliedDiscounts { - if applied.Condition.User != nil && *applied.Condition.User != "" { - _, err := api.clients.discount.DeleteDiscount(ctx.Context(), &discount.GetDiscountByIDRequest{ - ID: applied.ID, - }) - - if err != nil { - return api.error(ctx, fiber.StatusInternalServerError, "failed delete discount by id:%s", applied.ID) - } - } - } - - // WithdrawAccountWalletMoney - - request := models.WithdrawAccountWallet{ - Money: int64(discountResponse.Price), - Account: account, - } - - if validate.IsStringEmpty(request.Account.Wallet.Currency) { - request.Account.Wallet.Currency = models.InternalCurrencyKey - } - - var updatedAccount *models.Account - - if request.Account.Wallet.Currency == models.InternalCurrencyKey { - accountx, err := api.account.ChangeWallet(ctx.Context(), request.Account.UserID, &models.Wallet{ - Cash: request.Account.Wallet.Cash - request.Money, - Money: request.Account.Wallet.Money - request.Money, - Spent: request.Account.Wallet.Spent + request.Money, - PurchasesAmount: request.Account.Wallet.PurchasesAmount, - Currency: request.Account.Wallet.Currency, - }) - if err != nil { - return api.errorOld(ctx, err) - } - updatedAccount = accountx - } else { - cash, err := api.clients.currency.Translate(ctx.Context(), &models.TranslateCurrency{ - Money: request.Money, - From: models.InternalCurrencyKey, - To: request.Account.Wallet.Currency, - }) - if err != nil { - return api.errorOld(ctx, err) - } - - accountx, err := api.account.ChangeWallet(ctx.Context(), request.Account.UserID, &models.Wallet{ - Cash: request.Account.Wallet.Cash - cash, - Money: request.Account.Wallet.Money - request.Money, - Spent: request.Account.Wallet.Spent + request.Money, - PurchasesAmount: request.Account.Wallet.PurchasesAmount, - Currency: request.Account.Wallet.Currency, - }) - if err != nil { - return api.errorOld(ctx, err) - } - - updatedAccount = accountx - } - - if _, err := api.history.Insert(ctx.Context(), &models.History{ - Key: models.CustomerHistoryKeyPayCart, - UserID: account.UserID, - Comment: "Успешная оплата корзины", - RawDetails: models.RawDetails{ - Tariffs: tariffs, - Price: int64(discountResponse.Price), - }, - }); err != nil { - return api.errorOld(ctx, err) - } - - // TODO: обработать ошибки при отправке сообщений - - sendErrors := make([]errors.Error, 0) - waitGroup := sync.WaitGroup{} - mutex := sync.Mutex{} - - for _, tariff := range tariffs { - waitGroup.Add(1) - - go func(currentTariff models.Tariff) { - defer waitGroup.Done() - - if err := api.producer.Send(ctx.Context(), userID, ¤tTariff); err != nil { - mutex.Lock() - defer mutex.Unlock() - sendErrors = append(sendErrors, err) - } - }(tariff) - } - - waitGroup.Wait() - - if len(sendErrors) > 0 { - for _, err := range sendErrors { - api.logger.Error("failed to send tariffs to broker on of ", zap.Error(err)) - } - return api.error(ctx, fiber.StatusInternalServerError, "failed to send tariffs to broker") - } - - if _, err := api.account.ClearCart(ctx.Context(), account.UserID); err != nil { - return api.errorOld(ctx, err) - } - - updatedAccount.Cart = []string{} - - return ctx.Status(fiber.StatusOK).JSON(updatedAccount) -} - -// Currency - -func (api *API2) GetCurrencies(ctx *fiber.Ctx) error { - currencyList, err := api.currency.FindCurrenciesList(ctx.Context(), models.DefaultCurrencyListName) - if err != nil && err.Type() != errors.ErrNotFound { - return api.errorOld(ctx, err) - } - - if err != nil && err.Type() == errors.ErrNotFound { - return ctx.Status(fiber.StatusOK).JSON([]string{}) - } - - return ctx.Status(fiber.StatusOK).JSON(currencyList) -} - -func (api *API2) UpdateCurrencies(ctx *fiber.Ctx) error { - var req struct { - items []string - } - if err := ctx.BodyParser(&req); err != nil { - return api.error(ctx, fiber.StatusBadRequest, "failed to bind currencies") - } - - currencies := req.items - - currencyList, err := api.currency.ReplaceCurrencies(ctx.Context(), &models.CurrencyList{ - Name: models.DefaultCurrencyListName, - Currencies: currencies, - }) - if err != nil && err.Type() != errors.ErrNotFound { - return api.errorOld(ctx, err) - } - - if err != nil && err.Type() == errors.ErrNotFound { - newCurrencyList, err := api.currency.Insert(ctx.Context(), &models.CurrencyList{ - Name: models.DefaultCurrencyListName, - Currencies: currencies, - }) - if err != nil && err.Type() != errors.ErrNotFound { - return api.errorOld(ctx, err) - } - return ctx.Status(fiber.StatusOK).JSON(newCurrencyList.Currencies) - } - - return ctx.Status(fiber.StatusOK).JSON(currencyList.Currencies) -} - -// History - -func (api *API2) GetHistory(ctx *fiber.Ctx) error { - var userID string - - pageStr := ctx.Query("page") - limitStr := ctx.Query("limit") - tipe := ctx.Query("type") - accountID := ctx.Query("accountID") - - if accountID != "" { - userID = accountID - } else { - id, ok := api.extractUserID(ctx) - if !ok || id == "" { - return api.noauth(ctx) - } - - userID = id - } - - limit, err := strconv.ParseInt(limitStr, 10, 64) - if err != nil { - return api.error(ctx, fiber.StatusBadRequest, "invalid limit format") - } - - page, err := strconv.ParseInt(pageStr, 10, 64) - if err != nil { - return api.error(ctx, fiber.StatusBadRequest, "invalid page format") - } - - dto := &history.GetHistories{ - UserID: userID, - Type: &tipe, - Pagination: &models.Pagination{ - Page: page, - Limit: limit, - }, - } - - count, err := api.history.CountAll(ctx.Context(), dto) - if err != nil { - return api.errorOld(ctx, err) - } - - if count == 0 { - returnHistories := models.PaginationResponse[models.History]{TotalPages: 0, Records: []models.History{}} - return ctx.Status(fiber.StatusOK).JSON(returnHistories) - } - - totalPages := int64(math.Ceil(float64(count) / float64(dto.Pagination.Limit))) - - histories, err := api.history.FindMany(ctx.Context(), dto) - if err != nil { - return api.errorOld(ctx, err) - } - - returnHistories := models.PaginationResponse[models.History]{ - TotalPages: totalPages, - Records: histories, - } - - return ctx.Status(fiber.StatusOK).JSON(returnHistories) -} - -// Wallet - -func (api *API2) RequestMoney(ctx *fiber.Ctx) error { - userID, ok := api.extractUserID(ctx) - if !ok || userID == "" { - return api.noauth(ctx) - } - - var request models.GetPaymentLinkBody - if err := ctx.BodyParser(&request); err != nil { - return api.error(ctx, fiber.StatusBadRequest, "failed to bind payment link") - } - - if err := utils.ValidateGetPaymentLinkBody(&request); err != nil { - return api.errorOld(ctx, err) - } - - link, err := api.GetPaymentLink(ctx.Context(), &models.GetPaymentLinkRequest{ - Body: &request, - UserID: userID, - ClientIP: ctx.IP(), - }) - if err != nil { - return api.errorOld(ctx, err) - } - - return ctx.Status(fiber.StatusOK).JSON(&models.GetPaymentLinkResponse{Link: link}) -} - -func (api *API2) ChangeCurrency(ctx *fiber.Ctx) error { - userID, ok := api.extractUserID(ctx) - if !ok || userID == "" { - return api.noauth(ctx) - } - - var request models.ChangeCurrency - if err := ctx.BodyParser(&request); err != nil { - return api.error(ctx, fiber.StatusBadRequest, "failed to bind currency") - } - - if validate.IsStringEmpty(request.Currency) { - return api.error(ctx, fiber.StatusBadRequest, "empty currency") - } - - currency := request.Currency - account, err := api.account.FindByUserID(ctx.Context(), userID) - if err != nil { - return api.errorOld(ctx, err) - } - - cash, err := api.clients.currency.Translate(ctx.Context(), &models.TranslateCurrency{ - Money: account.Wallet.Cash, - From: account.Wallet.Currency, - To: currency, - }) - if err != nil { - return api.errorOld(ctx, err) - } - - updatedAccount, err := api.account.ChangeWallet(ctx.Context(), account.UserID, &models.Wallet{ - Cash: cash, - Currency: currency, - Money: account.Wallet.Money, - }) - if err != nil { - return api.errorOld(ctx, err) - } - - return ctx.Status(fiber.StatusOK).JSON(updatedAccount) -} - -func (api *API2) CalculateLTV(ctx *fiber.Ctx) error { - var req struct { - From int64 `json:"from"` - To int64 `json:"to"` - } - - if err := ctx.BodyParser(&req); err != nil { - api.logger.Error("failed to bind request", zap.Error(err)) - return api.error(ctx, fiber.StatusBadRequest, "failed to bind request") - } - - if req.From > req.To && req.To != 0 { - api.logger.Error("From timestamp must be less than To timestamp unless To is 0") - return api.error(ctx, fiber.StatusBadRequest, "From timestamp must be less than To timestamp unless To is 0") - } - - ltv, err := api.history.CalculateCustomerLTV(ctx.Context(), req.From, req.To) - if err != nil { - api.logger.Error("failed to calculate LTV", zap.Error(err)) - return api.errorOld(ctx, err) - } - - response := struct { - LTV int64 `json:"LTV"` - }{ - LTV: ltv, - } - - return ctx.Status(fiber.StatusOK).JSON(response) -} - -func (api *API2) GetRecentTariffs(ctx *fiber.Ctx) error { - userID, ok := api.extractUserID(ctx) - if !ok || userID == "" { - return api.noauth(ctx) - } - - tariffs, err := api.history.GetRecentTariffs(ctx.Context(), userID) - if err != nil { - api.logger.Error("failed to get recent tariffs on of ", - zap.String("userId", userID), - zap.Error(err), - ) - return api.errorOld(ctx, err) - } - - return ctx.Status(fiber.StatusOK).JSON(tariffs) -} - -func (api *API2) SendReport(ctx *fiber.Ctx) error { - var req struct { - Id string `json:"id"` - } - if err := ctx.BodyParser(&req); err != nil { - api.logger.Error("failed to bind request", zap.Error(err)) - return api.error(ctx, fiber.StatusBadRequest, "failed to bind request") - } - - if req.Id == "" { - api.logger.Error("history id is missing in of ") - return api.error(ctx, fiber.StatusBadRequest, "history id is missing") - } - - tariffs, err := api.history.GetHistoryByID(ctx.Context(), req.Id) - if err != nil { - api.logger.Error( - "failed to get history by id in of ", - zap.String("historyID", req.Id), - zap.Error(err), - ) - return api.errorOld(ctx, err) - } - - if tariffs.Key != models.CustomerHistoryKeyPayCart { - api.logger.Error( - "invalid history record key", - zap.String("historyID", req.Id), - zap.Error(err), - ) - return api.error(ctx, fiber.StatusBadRequest, "invalid history record key") - } - - historyMap, err := api.history.GetDocNumber(ctx.Context(), tariffs.UserID) - if err != nil { - api.logger.Error( - "failed to get history of sorting by date created in of ", - zap.String("historyID", req.Id), - zap.Error(err), - ) - return api.errorOld(ctx, err) - } - token := ctx.Get("Authorization") - fmt.Println("HEADERS", ctx.Request().Header) - - verifuser, err := api.clients.verify.GetVerification(ctx.Context(), token, tariffs.UserID) - if err != nil { - api.logger.Error("failed to get user verification on of ", - zap.Error(err), - zap.String("userID", tariffs.UserID), - ) - return api.errorOld(ctx, err) - } - if !verifuser.Accepted { - api.logger.Error( - "verification not accepted", - zap.String("userID", tariffs.UserID), - zap.Error(err), - ) - return api.error(ctx, fiber.StatusBadRequest, "verification not accepted") - } - account, err := api.account.FindByUserID(ctx.Context(), tariffs.UserID) - if err != nil { - return api.errorOld(ctx, err) - } - - authuser, err := api.clients.auth.GetUser(ctx.Context(), tariffs.UserID) - if err != nil { - api.logger.Error("failed to get user on of ", - zap.Error(err), - zap.String("userID", tariffs.UserID), - ) - return api.errorOld(ctx, err) - } - - fileContents, readerr := os.ReadFile("./report.docx") - if readerr != nil { - return api.error(ctx, fiber.StatusInternalServerError, "failed to read file") - } - - if account.Name.Orgname == "" { - account.Name.Orgname = "Безымянное предприятие" - } - - for _, tariff := range tariffs.RawDetails.Tariffs { - totalAmount := uint64(0) - privilegeMeasurement := "" - piecePrice := "" - privilegeName := "" - for _, privilege := range tariff.Privileges { - totalAmount += privilege.Amount - privilegeMeasurement = string(privilege.Type) - piecePrice = fmt.Sprintf("%.2f", float64(privilege.Price)/100) - privilegeName = privilege.Name - } - data := models.RespGeneratorService{ - DocNumber: fmt.Sprint(historyMap[req.Id] + 1), - Date: time.Now().Format("2006-01-02"), - OrgTaxNum: verifuser.TaxNumber, - OrgName: account.Name.Orgname, - Name: tariff.Name + " " + privilegeName, - Amount: fmt.Sprint(totalAmount), - Unit: piecePrice, - Price: fmt.Sprintf("%.2f", float64(tariffs.RawDetails.Price)/100), - Sum: privilegeMeasurement, - } - err = api.clients.template.SendData(ctx.Context(), data, fileContents, authuser.Login) - if err != nil { - api.logger.Error("failed to send report to user on of ", - zap.Error(err), - zap.String("userID", tariffs.UserID), - ) - return api.errorOld(ctx, err) - } - } - - return ctx.SendStatus(fiber.StatusOK) -} - -func (api *API2) PostWalletRspay(ctx *fiber.Ctx) error { - userID, ok := api.extractUserID(ctx) - if !ok || userID == "" { - return api.noauth(ctx) - } - - var req struct { - Money *float32 `json:"money,omitempty"` - } - - if err := ctx.BodyParser(&req); err != nil { - api.logger.Error("failed to bind request", zap.Error(err)) - return api.error(ctx, fiber.StatusBadRequest, "failed to bind request") - } - - user, err := api.account.FindByUserID(ctx.Context(), userID) - if err != nil { - return api.errorOld(ctx, err) - } - - if user.Status != models.AccountStatusNko && user.Status != models.AccountStatusOrg { - return api.error(ctx, fiber.StatusForbidden, "not allowed for non organizations") - } - token := ctx.Get("Authorization") - fmt.Println("HEADERS", ctx.Request().Header) - - verification, err := api.clients.verify.GetVerification(ctx.Context(), token, userID) - if err == errors.ErrNotFound { - return api.error(ctx, fiber.StatusForbidden, "no verification data found") - } - - if (user.Status == models.AccountStatusOrg && len(verification.Files) != 3) || - (user.Status == models.AccountStatusNko && len(verification.Files) != 4) { - return api.error(ctx, fiber.StatusForbidden, "not enough verification files") - } - - authData, err := api.clients.auth.GetUser(ctx.Context(), userID) - if err != nil { - return api.errorOld(ctx, err) - } - - err = api.clients.mail.SendMessage(authData.Login, verification, *req.Money) - if err != nil { - return api.errorOld(ctx, err) - } - - return ctx.SendStatus(fiber.StatusOK) -} - -type QuizLogoStat2 struct { - Count int64 `json:"count,omitempty"` - Items map[string]Item `json:"items,omitempty"` -} - -type Item struct { - Money int64 `json:"money,omitempty"` - Quizes map[string][2]int `json:"quizes,omitempty"` - Regs int `json:"regs,omitempty"` -} - -func (api *API2) QuizLogoStat(ctx *fiber.Ctx) error { - var req struct { - From *int `json:"from,omitempty"` - Limit *int `json:"limit,omitempty"` - Page *int `json:"page,omitempty"` - To *int `json:"to,omitempty"` - } - - if err := ctx.BodyParser(&req); err != nil { - api.logger.Error("failed to bind request", zap.Error(err)) - return api.error(ctx, fiber.StatusBadRequest, "failed to bind request") - } - - result, err := api.account.QuizLogoStat(ctx.Context(), repository.QuizLogoStatDeps{ - Page: req.Page, - Limit: req.Limit, - From: req.From, - To: req.To, - }) - if err != nil { - return api.error(ctx, fiber.StatusInternalServerError, fmt.Sprint("failed getting quiz logo stat", err.Error())) - } - - return ctx.Status(fiber.StatusOK).JSON(result) -} - -func (api *API2) PromocodeLTV(ctx *fiber.Ctx) error { - var req struct { - From int `json:"from"` - To int `json:"to"` - } - - if err := ctx.BodyParser(&req); err != nil { - api.logger.Error("failed to bind request", zap.Error(err)) - return api.error(ctx, fiber.StatusBadRequest, "failed to bind request") - } - - // получаем мапу вида [promoID] = []{userid,timeActivate} - // отдаются только первые использованые на аккаунте промокоды, соответсвенно подсчет идет сугубо по ним - // если в запросе время различается с временем активации - если меньше, то учитывается только после применения - // если больше, то учитывается только с начала переданного from - codewordData, err := api.clients.codeword.GetAllPromoActivations(ctx.Context(), &codeword_rpc.Time{ - To: int64(req.To), - From: int64(req.From), - }) - - if err != nil { - return api.error(ctx, fiber.StatusInternalServerError, fmt.Sprint("failed getting codeword data", err.Error())) - } - - userSumMap, er := api.history.GetPayUsersPromoHistory(ctx.Context(), codewordData, int64(req.From), int64(req.To)) - if er != nil { - return api.error(ctx, fiber.StatusInternalServerError, fmt.Sprint("failed calculate promo users paid sum", er.Error())) - } - - resp := make(map[string]struct { - Regs int - Money int64 - }) - - for promoID, data := range codewordData { - fmt.Println("PROTOMOTO", promoID, data) - for _, value := range data { - - paids, ok := userSumMap[value.UserID] - if !ok { - paids = 0 - } - fmt.Println("PROTOMOTO1", paids, value) - - if value.Time >= int64(req.From) && value.Time <= int64(req.To) { - if _, ok := resp[promoID]; !ok { - resp[promoID] = struct { - Regs int - Money int64 - }{Regs: 1, Money: paids} - continue - } - current := resp[promoID] - current.Regs += 1 - current.Money += paids - resp[promoID] = current - } - } - } - - return ctx.Status(fiber.StatusOK).JSON(resp) -} diff --git a/internal/interface/controller/http/currency/controllers.go b/internal/interface/controller/http/currency/controllers.go new file mode 100644 index 0000000..4290893 --- /dev/null +++ b/internal/interface/controller/http/currency/controllers.go @@ -0,0 +1,75 @@ +package currency + +import ( + "github.com/gofiber/fiber/v2" + "go.uber.org/zap" + "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/errors" + "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/interface/controller/http" + "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/interface/repository" + "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/models" +) + +type Deps struct { + CurrencyRepo *repository.CurrencyRepository + MiddleWare *http.MiddleWare + Logger *zap.Logger +} + +type CurrencyController struct { + currencyRepo *repository.CurrencyRepository + middleWare *http.MiddleWare + logger *zap.Logger +} + +func NewCurrencyController(deps Deps) *CurrencyController { + return &CurrencyController{ + currencyRepo: deps.CurrencyRepo, + middleWare: deps.MiddleWare, + logger: deps.Logger, + } +} + +func (receiver *CurrencyController) GetCurrencies(ctx *fiber.Ctx) error { + currencyList, err := receiver.currencyRepo.FindCurrenciesList(ctx.Context(), models.DefaultCurrencyListName) + if err != nil && err.Type() != errors.ErrNotFound { + return receiver.middleWare.ErrorOld(ctx, err) + } + + if err != nil && err.Type() == errors.ErrNotFound { + return ctx.Status(fiber.StatusOK).JSON([]string{}) + } + + return ctx.Status(fiber.StatusOK).JSON(currencyList) +} + +func (receiver *CurrencyController) UpdateCurrencies(ctx *fiber.Ctx) error { + var req struct { + items []string + } + if err := ctx.BodyParser(&req); err != nil { + return receiver.middleWare.Error(ctx, fiber.StatusBadRequest, "failed to bind currencies") + } + + currencies := req.items + + currencyList, err := receiver.currencyRepo.ReplaceCurrencies(ctx.Context(), &models.CurrencyList{ + Name: models.DefaultCurrencyListName, + Currencies: currencies, + }) + if err != nil && err.Type() != errors.ErrNotFound { + return receiver.middleWare.ErrorOld(ctx, err) + } + + if err != nil && err.Type() == errors.ErrNotFound { + newCurrencyList, err := receiver.currencyRepo.Insert(ctx.Context(), &models.CurrencyList{ + Name: models.DefaultCurrencyListName, + Currencies: currencies, + }) + if err != nil && err.Type() != errors.ErrNotFound { + return receiver.middleWare.ErrorOld(ctx, err) + } + return ctx.Status(fiber.StatusOK).JSON(newCurrencyList.Currencies) + } + + return ctx.Status(fiber.StatusOK).JSON(currencyList.Currencies) +} diff --git a/internal/interface/controller/http/currency/route.go b/internal/interface/controller/http/currency/route.go new file mode 100644 index 0000000..e096de4 --- /dev/null +++ b/internal/interface/controller/http/currency/route.go @@ -0,0 +1,12 @@ +package currency + +import "github.com/gofiber/fiber/v2" + +func (receiver *CurrencyController) Register(router fiber.Router) { + router.Get("/currencies", receiver.GetCurrencies) + router.Put("/currencies", receiver.UpdateCurrencies) +} + +func (receiver *CurrencyController) Name() string { + return "" +} diff --git a/internal/interface/controller/http/history/controllers.go b/internal/interface/controller/http/history/controllers.go new file mode 100644 index 0000000..566c5cb --- /dev/null +++ b/internal/interface/controller/http/history/controllers.go @@ -0,0 +1,383 @@ +package history + +import ( + "fmt" + "github.com/gofiber/fiber/v2" + "go.uber.org/zap" + "math" + "os" + "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/interface/client" + "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/interface/controller/http" + "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/interface/repository" + "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/models" + codeword_rpc "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/proto/codeword" + "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/service/history" + "strconv" + "time" +) + +type Deps struct { + MiddleWare *http.MiddleWare + HistoryRepo *repository.HistoryRepository + AccountRepo *repository.AccountRepository + VerifyClient *client.VerificationClient + AuthClient *client.AuthClient + TemplateClient *client.TemplateClient + CodewordClient *client.CodewordClient + Logger *zap.Logger +} + +type HistoryController struct { + middleWare *http.MiddleWare + historyRepo *repository.HistoryRepository + accountRepo *repository.AccountRepository + verifyClient *client.VerificationClient + authClient *client.AuthClient + templateClient *client.TemplateClient + codewordClient *client.CodewordClient + logger *zap.Logger +} + +func NewHistoryController(deps Deps) *HistoryController { + return &HistoryController{ + middleWare: deps.MiddleWare, + historyRepo: deps.HistoryRepo, + authClient: deps.AuthClient, + accountRepo: deps.AccountRepo, + verifyClient: deps.VerifyClient, + templateClient: deps.TemplateClient, + codewordClient: deps.CodewordClient, + logger: deps.Logger, + } +} + +func (receiver *HistoryController) GetHistory(ctx *fiber.Ctx) error { + var userID string + + pageStr := ctx.Query("page") + limitStr := ctx.Query("limit") + tipe := ctx.Query("type") + accountID := ctx.Query("accountID") + + if accountID != "" { + userID = accountID + } else { + id, ok := receiver.middleWare.ExtractUserID(ctx) + if !ok || id == "" { + return receiver.middleWare.NoAuth(ctx) + } + + userID = id + } + + limit, err := strconv.ParseInt(limitStr, 10, 64) + if err != nil { + return receiver.middleWare.Error(ctx, fiber.StatusBadRequest, "invalid limit format") + } + + page, err := strconv.ParseInt(pageStr, 10, 64) + if err != nil { + return receiver.middleWare.Error(ctx, fiber.StatusBadRequest, "invalid page format") + } + + dto := &history.GetHistories{ + UserID: userID, + Type: &tipe, + Pagination: &models.Pagination{ + Page: page, + Limit: limit, + }, + } + + count, err := receiver.historyRepo.CountAll(ctx.Context(), dto) + if err != nil { + return receiver.middleWare.ErrorOld(ctx, err) + } + + if count == 0 { + returnHistories := models.PaginationResponse[models.History]{TotalPages: 0, Records: []models.History{}} + return ctx.Status(fiber.StatusOK).JSON(returnHistories) + } + + totalPages := int64(math.Ceil(float64(count) / float64(dto.Pagination.Limit))) + + histories, err := receiver.historyRepo.FindMany(ctx.Context(), dto) + if err != nil { + return receiver.middleWare.ErrorOld(ctx, err) + } + + returnHistories := models.PaginationResponse[models.History]{ + TotalPages: totalPages, + Records: histories, + } + + return ctx.Status(fiber.StatusOK).JSON(returnHistories) +} + +func (receiver *HistoryController) CalculateLTV(ctx *fiber.Ctx) error { + var req struct { + From int64 `json:"from"` + To int64 `json:"to"` + } + + if err := ctx.BodyParser(&req); err != nil { + receiver.logger.Error("failed to bind request", zap.Error(err)) + return receiver.middleWare.Error(ctx, fiber.StatusBadRequest, "failed to bind request") + } + + if req.From > req.To && req.To != 0 { + receiver.logger.Error("From timestamp must be less than To timestamp unless To is 0") + return receiver.middleWare.Error(ctx, fiber.StatusBadRequest, "From timestamp must be less than To timestamp unless To is 0") + } + + ltv, err := receiver.historyRepo.CalculateCustomerLTV(ctx.Context(), req.From, req.To) + if err != nil { + receiver.logger.Error("failed to calculate LTV", zap.Error(err)) + return receiver.middleWare.ErrorOld(ctx, err) + } + + response := struct { + LTV int64 `json:"LTV"` + }{ + LTV: ltv, + } + + return ctx.Status(fiber.StatusOK).JSON(response) +} + +func (receiver *HistoryController) GetRecentTariffs(ctx *fiber.Ctx) error { + userID, ok := receiver.middleWare.ExtractUserID(ctx) + if !ok || userID == "" { + return receiver.middleWare.NoAuth(ctx) + } + + tariffs, err := receiver.historyRepo.GetRecentTariffs(ctx.Context(), userID) + if err != nil { + receiver.logger.Error("failed to get recent tariffs on of ", + zap.String("userId", userID), + zap.Error(err), + ) + return receiver.middleWare.ErrorOld(ctx, err) + } + + return ctx.Status(fiber.StatusOK).JSON(tariffs) +} + +func (receiver *HistoryController) SendReport(ctx *fiber.Ctx) error { + var req struct { + Id string `json:"id"` + } + if err := ctx.BodyParser(&req); err != nil { + receiver.logger.Error("failed to bind request", zap.Error(err)) + return receiver.middleWare.Error(ctx, fiber.StatusBadRequest, "failed to bind request") + } + + if req.Id == "" { + receiver.logger.Error("history id is missing in of ") + return receiver.middleWare.Error(ctx, fiber.StatusBadRequest, "history id is missing") + } + + tariffs, err := receiver.historyRepo.GetHistoryByID(ctx.Context(), req.Id) + if err != nil { + receiver.logger.Error( + "failed to get history by id in of ", + zap.String("historyID", req.Id), + zap.Error(err), + ) + return receiver.middleWare.ErrorOld(ctx, err) + } + + if tariffs.Key != models.CustomerHistoryKeyPayCart { + receiver.logger.Error( + "invalid history record key", + zap.String("historyID", req.Id), + zap.Error(err), + ) + return receiver.middleWare.Error(ctx, fiber.StatusBadRequest, "invalid history record key") + } + + historyMap, err := receiver.historyRepo.GetDocNumber(ctx.Context(), tariffs.UserID) + if err != nil { + receiver.logger.Error( + "failed to get history of sorting by date created in of ", + zap.String("historyID", req.Id), + zap.Error(err), + ) + return receiver.middleWare.ErrorOld(ctx, err) + } + token := ctx.Get("Authorization") + fmt.Println("HEADERS", ctx.Request().Header) + + verifuser, err := receiver.verifyClient.GetVerification(ctx.Context(), token, tariffs.UserID) + if err != nil { + receiver.logger.Error("failed to get user verification on of ", + zap.Error(err), + zap.String("userID", tariffs.UserID), + ) + return receiver.middleWare.ErrorOld(ctx, err) + } + if !verifuser.Accepted { + receiver.logger.Error( + "verification not accepted", + zap.String("userID", tariffs.UserID), + zap.Error(err), + ) + return receiver.middleWare.Error(ctx, fiber.StatusBadRequest, "verification not accepted") + } + account, err := receiver.accountRepo.FindByUserID(ctx.Context(), tariffs.UserID) + if err != nil { + return receiver.middleWare.ErrorOld(ctx, err) + } + + authuser, err := receiver.authClient.GetUser(ctx.Context(), tariffs.UserID) + if err != nil { + receiver.logger.Error("failed to get user on of ", + zap.Error(err), + zap.String("userID", tariffs.UserID), + ) + return receiver.middleWare.ErrorOld(ctx, err) + } + + fileContents, readerr := os.ReadFile("./report.docx") + if readerr != nil { + return receiver.middleWare.Error(ctx, fiber.StatusInternalServerError, "failed to read file") + } + + if account.Name.Orgname == "" { + account.Name.Orgname = "Безымянное предприятие" + } + + for _, tariff := range tariffs.RawDetails.Tariffs { + totalAmount := uint64(0) + privilegeMeasurement := "" + piecePrice := "" + privilegeName := "" + for _, privilege := range tariff.Privileges { + totalAmount += privilege.Amount + privilegeMeasurement = string(privilege.Type) + piecePrice = fmt.Sprintf("%.2f", float64(privilege.Price)/100) + privilegeName = privilege.Name + } + data := models.RespGeneratorService{ + DocNumber: fmt.Sprint(historyMap[req.Id] + 1), + Date: time.Now().Format("2006-01-02"), + OrgTaxNum: verifuser.TaxNumber, + OrgName: account.Name.Orgname, + Name: tariff.Name + " " + privilegeName, + Amount: fmt.Sprint(totalAmount), + Unit: piecePrice, + Price: fmt.Sprintf("%.2f", float64(tariffs.RawDetails.Price)/100), + Sum: privilegeMeasurement, + } + err = receiver.templateClient.SendData(ctx.Context(), data, fileContents, authuser.Login) + if err != nil { + receiver.logger.Error("failed to send report to user on of ", + zap.Error(err), + zap.String("userID", tariffs.UserID), + ) + return receiver.middleWare.ErrorOld(ctx, err) + } + } + + return ctx.SendStatus(fiber.StatusOK) +} + +type QuizLogoStat2 struct { + Count int64 `json:"count,omitempty"` + Items map[string]Item `json:"items,omitempty"` +} + +type Item struct { + Money int64 `json:"money,omitempty"` + Quizes map[string][2]int `json:"quizes,omitempty"` + Regs int `json:"regs,omitempty"` +} + +func (receiver *HistoryController) QuizLogoStat(ctx *fiber.Ctx) error { + var req struct { + From *int `json:"from,omitempty"` + Limit *int `json:"limit,omitempty"` + Page *int `json:"page,omitempty"` + To *int `json:"to,omitempty"` + } + + if err := ctx.BodyParser(&req); err != nil { + receiver.logger.Error("failed to bind request", zap.Error(err)) + return receiver.middleWare.Error(ctx, fiber.StatusBadRequest, "failed to bind request") + } + + result, err := receiver.accountRepo.QuizLogoStat(ctx.Context(), repository.QuizLogoStatDeps{ + Page: req.Page, + Limit: req.Limit, + From: req.From, + To: req.To, + }) + if err != nil { + return receiver.middleWare.Error(ctx, fiber.StatusInternalServerError, fmt.Sprint("failed getting quiz logo stat", err.Error())) + } + + return ctx.Status(fiber.StatusOK).JSON(result) +} + +func (receiver *HistoryController) PromocodeLTV(ctx *fiber.Ctx) error { + var req struct { + From int `json:"from"` + To int `json:"to"` + } + + if err := ctx.BodyParser(&req); err != nil { + receiver.logger.Error("failed to bind request", zap.Error(err)) + return receiver.middleWare.Error(ctx, fiber.StatusBadRequest, "failed to bind request") + } + + // получаем мапу вида [promoID] = []{userid,timeActivate} + // отдаются только первые использованые на аккаунте промокоды, соответсвенно подсчет идет сугубо по ним + // если в запросе время различается с временем активации - если меньше, то учитывается только после применения + // если больше, то учитывается только с начала переданного from + codewordData, err := receiver.codewordClient.GetAllPromoActivations(ctx.Context(), &codeword_rpc.Time{ + To: int64(req.To), + From: int64(req.From), + }) + + if err != nil { + return receiver.middleWare.Error(ctx, fiber.StatusInternalServerError, fmt.Sprint("failed getting codeword data", err.Error())) + } + + userSumMap, er := receiver.historyRepo.GetPayUsersPromoHistory(ctx.Context(), codewordData, int64(req.From), int64(req.To)) + if er != nil { + return receiver.middleWare.Error(ctx, fiber.StatusInternalServerError, fmt.Sprint("failed calculate promo users paid sum", er.Error())) + } + + resp := make(map[string]struct { + Regs int + Money int64 + }) + + for promoID, data := range codewordData { + fmt.Println("PROTOMOTO", promoID, data) + for _, value := range data { + + paids, ok := userSumMap[value.UserID] + if !ok { + paids = 0 + } + fmt.Println("PROTOMOTO1", paids, value) + + if value.Time >= int64(req.From) && value.Time <= int64(req.To) { + if _, ok := resp[promoID]; !ok { + resp[promoID] = struct { + Regs int + Money int64 + }{Regs: 1, Money: paids} + continue + } + current := resp[promoID] + current.Regs += 1 + current.Money += paids + resp[promoID] = current + } + } + } + + return ctx.Status(fiber.StatusOK).JSON(resp) +} diff --git a/internal/interface/controller/http/history/route.go b/internal/interface/controller/http/history/route.go new file mode 100644 index 0000000..0edf71f --- /dev/null +++ b/internal/interface/controller/http/history/route.go @@ -0,0 +1,16 @@ +package history + +import "github.com/gofiber/fiber/v2" + +func (receiver *HistoryController) Register(router fiber.Router) { + router.Get("/history", receiver.GetHistory) + router.Post("/history/ltv", receiver.CalculateLTV) + router.Post("/promocode/ltv", receiver.PromocodeLTV) + router.Post("/quizlogo/stat", receiver.QuizLogoStat) + router.Get("/recent", receiver.GetRecentTariffs) + router.Post("/sendReport", receiver.SendReport) +} + +func (receiver *HistoryController) Name() string { + return "" +} diff --git a/internal/interface/controller/http/middleware.go b/internal/interface/controller/http/middleware.go new file mode 100644 index 0000000..53f686d --- /dev/null +++ b/internal/interface/controller/http/middleware.go @@ -0,0 +1,55 @@ +package http + +import ( + "fmt" + "github.com/gofiber/fiber/v2" + "go.uber.org/zap" + "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/models" +) + +type MiddleWare struct { + logger *zap.Logger +} + +func NewMiddleWare(logger *zap.Logger) *MiddleWare { + return &MiddleWare{ + logger: logger, + } +} + +func (mw *MiddleWare) Error(ctx *fiber.Ctx, status int, message string, rest ...any) error { + if len(rest) > 0 { + message = fmt.Sprintf(message, rest...) + } + mw.logger.Error(message) + return ctx.Status(status).JSON(models.ResponseErrorHTTP{ + StatusCode: status, + Message: message, + }) +} + +func (mw *MiddleWare) ErrorOld(ctx *fiber.Ctx, err error) error { + mw.logger.Error("error:", zap.Error(err)) + return ctx.Status(fiber.StatusInternalServerError).JSON(models.ResponseErrorHTTP{ + StatusCode: fiber.StatusInternalServerError, + Message: err.Error(), + }) +} + +func (mw *MiddleWare) NoAuth(ctx *fiber.Ctx) error { + return mw.Error(ctx, fiber.StatusUnauthorized, "failed to get jwt payload") +} + +func (mw *MiddleWare) ExtractUserID(ctx *fiber.Ctx) (string, bool) { + id, ok := ctx.Context().UserValue(models.AuthJWTDecodedUserIDKey).(string) + return id, ok +} + +func (mw *MiddleWare) ExtractToken(ctx *fiber.Ctx) (string, bool) { + token, ok := ctx.Context().UserValue(models.AuthJWTDecodedAccessTokenKey).(string) + return token, ok +} + +func (mw *MiddleWare) GetHealth(ctx *fiber.Ctx) error { + return ctx.Status(fiber.StatusOK).SendString("OK") +} diff --git a/internal/interface/controller/http/money.go b/internal/interface/controller/http/money.go deleted file mode 100644 index 9beea2d..0000000 --- a/internal/interface/controller/http/money.go +++ /dev/null @@ -1,152 +0,0 @@ -package http - -import ( - "context" - - "go.uber.org/zap" - "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/errors" - "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/models" - "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/proto/treasurer" -) - -func (api *API2) GetPaymentLink(ctx context.Context, request *models.GetPaymentLinkRequest) (string, errors.Error) { - if _, userErr := api.clients.auth.GetUser(ctx, request.UserID); userErr != nil { - api.logger.Error("failed to get user on on ", - zap.Error(userErr), - zap.String("userID", request.UserID), - ) - - return "", userErr - } - - switch request.Body.Type { - case models.PaymentTypeBankCard: - return api.GetPaymentLinkBankCard(ctx, request) - case models.PaymentTypeYoomoney: - return api.GetPaymentLinkYooMoney(ctx, request) - case models.PaymentTypeSberPay: - return api.GetPaymentLinkSberPay(ctx, request) - case models.PaymentTypeTinkoff: - return api.GetPaymentLinkTinkoff(ctx, request) - case models.PaymentTypeSBP: - return api.GetPaymentLinkSBP(ctx, request) - case models.PaymentTypeSberB2B: - return api.GetPaymentLinkB2B(ctx, request) - } - - return "", errors.NewWithMessage("invalid payment method type", errors.ErrInvalidArgs) -} - -func (api *API2) GetPaymentLinkBankCard(ctx context.Context, request *models.GetPaymentLinkRequest) (string, errors.Error) { - link, err := api.clients.payment.GetPaymentLinkBankCard(ctx, &treasurer.GetBankCardPaymentLinkRequest{ - MainSettings: &treasurer.MainPaymentSettings{ - Currency: request.Body.Currency, - Amount: request.Body.Amount, - UserID: request.UserID, - ClientIP: request.ClientIP, - CallbackHostGRPC: []string{api.grpc.Domen}, - ReturnURL: request.Body.ReturnURL, - }, - }) - if err != nil { - api.logger.Error("failed to get bankcard payment link on of ", zap.Error(err)) - return "", err - } - - return link, nil -} - -func (api *API2) GetPaymentLinkYooMoney(ctx context.Context, request *models.GetPaymentLinkRequest) (string, errors.Error) { - link, err := api.clients.payment.GetPaymentLinkYooMoney(ctx, &treasurer.GetPaymentLinkBody{ - MainSettings: &treasurer.MainPaymentSettings{ - Currency: request.Body.Currency, - Amount: request.Body.Amount, - UserID: request.UserID, - ClientIP: request.ClientIP, - CallbackHostGRPC: []string{api.grpc.Domen}, - ReturnURL: request.Body.ReturnURL, - }, - }) - if err != nil { - api.logger.Error("failed to get yoomoney payment link on of ", zap.Error(err)) - return "", err - } - - return link, nil -} - -func (api *API2) GetPaymentLinkSberPay(ctx context.Context, request *models.GetPaymentLinkRequest) (string, errors.Error) { - link, err := api.clients.payment.GetPaymentLinkSberPay(ctx, &treasurer.GetPaymentLinkBody{ - MainSettings: &treasurer.MainPaymentSettings{ - Currency: request.Body.Currency, - Amount: request.Body.Amount, - UserID: request.UserID, - ClientIP: request.ClientIP, - CallbackHostGRPC: []string{api.grpc.Domen}, - ReturnURL: request.Body.ReturnURL, - }, - }) - if err != nil { - api.logger.Error("failed to get sberpay payment link on of ", zap.Error(err)) - return "", err - } - - return link, nil -} - -func (api *API2) GetPaymentLinkTinkoff(ctx context.Context, request *models.GetPaymentLinkRequest) (string, errors.Error) { - link, err := api.clients.payment.GetPaymentLinkTinkoff(ctx, &treasurer.GetPaymentLinkBody{ - MainSettings: &treasurer.MainPaymentSettings{ - Currency: request.Body.Currency, - Amount: request.Body.Amount, - UserID: request.UserID, - ClientIP: request.ClientIP, - CallbackHostGRPC: []string{api.grpc.Domen}, - ReturnURL: request.Body.ReturnURL, - }, - }) - if err != nil { - api.logger.Error("failed to get tinkoff payment link on of ", zap.Error(err)) - return "", err - } - - return link, nil -} - -func (api *API2) GetPaymentLinkSBP(ctx context.Context, request *models.GetPaymentLinkRequest) (string, errors.Error) { - link, err := api.clients.payment.GetPaymentLinkSBP(ctx, &treasurer.GetPaymentLinkBody{ - MainSettings: &treasurer.MainPaymentSettings{ - Currency: request.Body.Currency, - Amount: request.Body.Amount, - UserID: request.UserID, - ClientIP: request.ClientIP, - CallbackHostGRPC: []string{api.grpc.Domen}, - ReturnURL: request.Body.ReturnURL, - }, - }) - if err != nil { - api.logger.Error("failed to get sbp payment link on of ", zap.Error(err)) - return "", err - } - - return link, nil -} - -func (api *API2) GetPaymentLinkB2B(ctx context.Context, request *models.GetPaymentLinkRequest) (string, errors.Error) { - link, err := api.clients.payment.GetPaymentLinkSberbankB2B(ctx, &treasurer.GetPaymentLinkBody{ - MainSettings: &treasurer.MainPaymentSettings{ - Currency: request.Body.Currency, - Amount: request.Body.Amount, - UserID: request.UserID, - ClientIP: request.ClientIP, - CallbackHostGRPC: []string{api.grpc.Domen}, - ReturnURL: request.Body.ReturnURL, - }, - }) - if err != nil { - api.logger.Error("failed to get sberbankb2b payment link on of ", zap.Error(err)) - return "", err - } - - return link, nil -} diff --git a/internal/interface/controller/http/route.go b/internal/interface/controller/http/route.go index ae876cb..36b7167 100644 --- a/internal/interface/controller/http/route.go +++ b/internal/interface/controller/http/route.go @@ -1,32 +1,30 @@ package http -import "github.com/gofiber/fiber/v2" - -func (api *API2) Register(router fiber.Router) { - router.Delete("/account", api.DeleteAccount) - router.Get("/account", api.GetAccount) - router.Patch("/account", api.ChangeAccount) - router.Post("/account", api.AddAccount) - router.Delete("/account/:userId", api.DeleteDirectAccount) - router.Get("/account/:userId", api.GetDirectAccount) - router.Patch("/account/:userId", api.SetAccountVerificationStatus) - router.Get("/accounts", api.PaginationAccounts) - router.Delete("/cart", api.RemoveFromCart) - router.Patch("/cart", api.Add2cart) - router.Post("/cart/pay", api.PayCart) - router.Get("/currencies", api.GetCurrencies) - router.Put("/currencies", api.UpdateCurrencies) - router.Get("/history", api.GetHistory) - router.Post("/history/ltv", api.CalculateLTV) - router.Post("/promocode/ltv", api.PromocodeLTV) - router.Post("/quizlogo/stat", api.QuizLogoStat) - router.Get("/recent", api.GetRecentTariffs) - router.Post("/sendReport", api.SendReport) - router.Patch("/wallet", api.ChangeCurrency) - router.Post("/wallet", api.RequestMoney) - router.Post("/wallet/rspay", api.PostWalletRspay) -} - -func (api *API2) Name() string { - return "" -} +//func (api *API2) Register(router fiber.Router) { +// router.Delete("/account", api.DeleteAccount) +// router.Get("/account", api.GetAccount) +// router.Patch("/account", api.ChangeAccount) +// router.Post("/account", api.AddAccount) +// router.Delete("/account/:userId", api.DeleteDirectAccount) +// router.Get("/account/:userId", api.GetDirectAccount) +// router.Patch("/account/:userId", api.SetAccountVerificationStatus) +// router.Get("/accounts", api.PaginationAccounts) +// router.Delete("/cart", api.RemoveFromCart) +// router.Patch("/cart", api.Add2cart) +// router.Post("/cart/pay", api.PayCart) +// router.Get("/currencies", api.GetCurrencies) +// router.Put("/currencies", api.UpdateCurrencies) +// router.Get("/history", api.GetHistory) +// router.Post("/history/ltv", api.CalculateLTV) +// router.Post("/promocode/ltv", api.PromocodeLTV) +// router.Post("/quizlogo/stat", api.QuizLogoStat) +// router.Get("/recent", api.GetRecentTariffs) +// router.Post("/sendReport", api.SendReport) +// router.Patch("/wallet", api.ChangeCurrency) +// router.Post("/wallet", api.RequestMoney) +// router.Post("/wallet/rspay", api.PostWalletRspay) +//} +// +//func (api *API2) Name() string { +// return "" +//} diff --git a/internal/interface/controller/http/wallet/controllers.go b/internal/interface/controller/http/wallet/controllers.go new file mode 100644 index 0000000..32896e9 --- /dev/null +++ b/internal/interface/controller/http/wallet/controllers.go @@ -0,0 +1,314 @@ +package wallet + +import ( + "context" + "fmt" + "github.com/gofiber/fiber/v2" + "go.uber.org/zap" + "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/errors" + "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/interface/client" + "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/interface/controller/http" + "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/interface/repository" + "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/models" + "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/proto/treasurer" + "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/utils" + "penahub.gitlab.yandexcloud.net/pena-services/customer/pkg/validate" +) + +type Deps struct { + MiddleWare *http.MiddleWare + AuthClient *client.AuthClient + PaymentClient *client.PaymentClient + GRPC *models.ConfigurationGRPC + AccountRepo *repository.AccountRepository + CurrencyClient *client.CurrencyClient + VerifyClient *client.VerificationClient + MailClient *client.MailClient + Logger *zap.Logger +} + +type WalletController struct { + middleWare *http.MiddleWare + authClient *client.AuthClient + paymentClient *client.PaymentClient + grpc *models.ConfigurationGRPC + accountRepo *repository.AccountRepository + currencyClient *client.CurrencyClient + verifyClient *client.VerificationClient + mailClient *client.MailClient + logger *zap.Logger +} + +func NewWalletController(deps Deps) *WalletController { + return &WalletController{ + middleWare: deps.MiddleWare, + authClient: deps.AuthClient, + paymentClient: deps.PaymentClient, + grpc: deps.GRPC, + accountRepo: deps.AccountRepo, + currencyClient: deps.CurrencyClient, + verifyClient: deps.VerifyClient, + mailClient: deps.MailClient, + logger: deps.Logger, + } +} + +func (receiver *WalletController) RequestMoney(ctx *fiber.Ctx) error { + userID, ok := receiver.middleWare.ExtractUserID(ctx) + if !ok || userID == "" { + return receiver.middleWare.NoAuth(ctx) + } + + var request models.GetPaymentLinkBody + if err := ctx.BodyParser(&request); err != nil { + return receiver.middleWare.Error(ctx, fiber.StatusBadRequest, "failed to bind payment link") + } + + if err := utils.ValidateGetPaymentLinkBody(&request); err != nil { + return receiver.middleWare.ErrorOld(ctx, err) + } + + link, err := receiver.GetPaymentLink(ctx.Context(), &models.GetPaymentLinkRequest{ + Body: &request, + UserID: userID, + ClientIP: ctx.IP(), + }) + if err != nil { + return receiver.middleWare.ErrorOld(ctx, err) + } + + return ctx.Status(fiber.StatusOK).JSON(&models.GetPaymentLinkResponse{Link: link}) +} + +func (receiver *WalletController) GetPaymentLink(ctx context.Context, request *models.GetPaymentLinkRequest) (string, errors.Error) { + if _, userErr := receiver.authClient.GetUser(ctx, request.UserID); userErr != nil { + receiver.logger.Error("failed to get user on on ", + zap.Error(userErr), + zap.String("userID", request.UserID), + ) + + return "", userErr + } + + switch request.Body.Type { + case models.PaymentTypeBankCard: + return receiver.GetPaymentLinkBankCard(ctx, request) + case models.PaymentTypeYoomoney: + return receiver.GetPaymentLinkYooMoney(ctx, request) + case models.PaymentTypeSberPay: + return receiver.GetPaymentLinkSberPay(ctx, request) + case models.PaymentTypeTinkoff: + return receiver.GetPaymentLinkTinkoff(ctx, request) + case models.PaymentTypeSBP: + return receiver.GetPaymentLinkSBP(ctx, request) + case models.PaymentTypeSberB2B: + return receiver.GetPaymentLinkB2B(ctx, request) + } + + return "", errors.NewWithMessage("invalid payment method type", errors.ErrInvalidArgs) +} + +func (receiver *WalletController) GetPaymentLinkBankCard(ctx context.Context, request *models.GetPaymentLinkRequest) (string, errors.Error) { + link, err := receiver.paymentClient.GetPaymentLinkBankCard(ctx, &treasurer.GetBankCardPaymentLinkRequest{ + MainSettings: &treasurer.MainPaymentSettings{ + Currency: request.Body.Currency, + Amount: request.Body.Amount, + UserID: request.UserID, + ClientIP: request.ClientIP, + CallbackHostGRPC: []string{receiver.grpc.Domen}, + ReturnURL: request.Body.ReturnURL, + }, + }) + if err != nil { + receiver.logger.Error("failed to get bankcard payment link on of ", zap.Error(err)) + return "", err + } + + return link, nil +} + +func (receiver *WalletController) GetPaymentLinkYooMoney(ctx context.Context, request *models.GetPaymentLinkRequest) (string, errors.Error) { + link, err := receiver.paymentClient.GetPaymentLinkYooMoney(ctx, &treasurer.GetPaymentLinkBody{ + MainSettings: &treasurer.MainPaymentSettings{ + Currency: request.Body.Currency, + Amount: request.Body.Amount, + UserID: request.UserID, + ClientIP: request.ClientIP, + CallbackHostGRPC: []string{receiver.grpc.Domen}, + ReturnURL: request.Body.ReturnURL, + }, + }) + if err != nil { + receiver.logger.Error("failed to get yoomoney payment link on of ", zap.Error(err)) + return "", err + } + + return link, nil +} + +func (receiver *WalletController) GetPaymentLinkSberPay(ctx context.Context, request *models.GetPaymentLinkRequest) (string, errors.Error) { + link, err := receiver.paymentClient.GetPaymentLinkSberPay(ctx, &treasurer.GetPaymentLinkBody{ + MainSettings: &treasurer.MainPaymentSettings{ + Currency: request.Body.Currency, + Amount: request.Body.Amount, + UserID: request.UserID, + ClientIP: request.ClientIP, + CallbackHostGRPC: []string{receiver.grpc.Domen}, + ReturnURL: request.Body.ReturnURL, + }, + }) + if err != nil { + receiver.logger.Error("failed to get sberpay payment link on of ", zap.Error(err)) + return "", err + } + + return link, nil +} + +func (receiver *WalletController) GetPaymentLinkTinkoff(ctx context.Context, request *models.GetPaymentLinkRequest) (string, errors.Error) { + link, err := receiver.paymentClient.GetPaymentLinkTinkoff(ctx, &treasurer.GetPaymentLinkBody{ + MainSettings: &treasurer.MainPaymentSettings{ + Currency: request.Body.Currency, + Amount: request.Body.Amount, + UserID: request.UserID, + ClientIP: request.ClientIP, + CallbackHostGRPC: []string{receiver.grpc.Domen}, + ReturnURL: request.Body.ReturnURL, + }, + }) + if err != nil { + receiver.logger.Error("failed to get tinkoff payment link on of ", zap.Error(err)) + return "", err + } + + return link, nil +} + +func (receiver *WalletController) GetPaymentLinkSBP(ctx context.Context, request *models.GetPaymentLinkRequest) (string, errors.Error) { + link, err := receiver.paymentClient.GetPaymentLinkSBP(ctx, &treasurer.GetPaymentLinkBody{ + MainSettings: &treasurer.MainPaymentSettings{ + Currency: request.Body.Currency, + Amount: request.Body.Amount, + UserID: request.UserID, + ClientIP: request.ClientIP, + CallbackHostGRPC: []string{receiver.grpc.Domen}, + ReturnURL: request.Body.ReturnURL, + }, + }) + if err != nil { + receiver.logger.Error("failed to get sbp payment link on of ", zap.Error(err)) + return "", err + } + + return link, nil +} + +func (receiver *WalletController) GetPaymentLinkB2B(ctx context.Context, request *models.GetPaymentLinkRequest) (string, errors.Error) { + link, err := receiver.paymentClient.GetPaymentLinkSberbankB2B(ctx, &treasurer.GetPaymentLinkBody{ + MainSettings: &treasurer.MainPaymentSettings{ + Currency: request.Body.Currency, + Amount: request.Body.Amount, + UserID: request.UserID, + ClientIP: request.ClientIP, + CallbackHostGRPC: []string{receiver.grpc.Domen}, + ReturnURL: request.Body.ReturnURL, + }, + }) + if err != nil { + receiver.logger.Error("failed to get sberbankb2b payment link on of ", zap.Error(err)) + return "", err + } + + return link, nil +} + +func (receiver *WalletController) ChangeCurrency(ctx *fiber.Ctx) error { + userID, ok := receiver.middleWare.ExtractUserID(ctx) + if !ok || userID == "" { + return receiver.middleWare.NoAuth(ctx) + } + + var request models.ChangeCurrency + if err := ctx.BodyParser(&request); err != nil { + return receiver.middleWare.Error(ctx, fiber.StatusBadRequest, "failed to bind currency") + } + + if validate.IsStringEmpty(request.Currency) { + return receiver.middleWare.Error(ctx, fiber.StatusBadRequest, "empty currency") + } + + currency := request.Currency + account, err := receiver.accountRepo.FindByUserID(ctx.Context(), userID) + if err != nil { + return receiver.middleWare.ErrorOld(ctx, err) + } + + cash, err := receiver.currencyClient.Translate(ctx.Context(), &models.TranslateCurrency{ + Money: account.Wallet.Cash, + From: account.Wallet.Currency, + To: currency, + }) + if err != nil { + return receiver.middleWare.ErrorOld(ctx, err) + } + + updatedAccount, err := receiver.accountRepo.ChangeWallet(ctx.Context(), account.UserID, &models.Wallet{ + Cash: cash, + Currency: currency, + Money: account.Wallet.Money, + }) + if err != nil { + return receiver.middleWare.ErrorOld(ctx, err) + } + + return ctx.Status(fiber.StatusOK).JSON(updatedAccount) +} + +func (receiver *WalletController) PostWalletRspay(ctx *fiber.Ctx) error { + userID, ok := receiver.middleWare.ExtractUserID(ctx) + if !ok || userID == "" { + return receiver.middleWare.NoAuth(ctx) + } + + var req struct { + Money *float32 `json:"money,omitempty"` + } + + if err := ctx.BodyParser(&req); err != nil { + receiver.logger.Error("failed to bind request", zap.Error(err)) + return receiver.middleWare.Error(ctx, fiber.StatusBadRequest, "failed to bind request") + } + + user, err := receiver.accountRepo.FindByUserID(ctx.Context(), userID) + if err != nil { + return receiver.middleWare.ErrorOld(ctx, err) + } + + if user.Status != models.AccountStatusNko && user.Status != models.AccountStatusOrg { + return receiver.middleWare.Error(ctx, fiber.StatusForbidden, "not allowed for non organizations") + } + token := ctx.Get("Authorization") + fmt.Println("HEADERS", ctx.Request().Header) + + verification, err := receiver.verifyClient.GetVerification(ctx.Context(), token, userID) + if err == errors.ErrNotFound { + return receiver.middleWare.Error(ctx, fiber.StatusForbidden, "no verification data found") + } + + if (user.Status == models.AccountStatusOrg && len(verification.Files) != 3) || + (user.Status == models.AccountStatusNko && len(verification.Files) != 4) { + return receiver.middleWare.Error(ctx, fiber.StatusForbidden, "not enough verification files") + } + + authData, err := receiver.authClient.GetUser(ctx.Context(), userID) + if err != nil { + return receiver.middleWare.ErrorOld(ctx, err) + } + + err = receiver.mailClient.SendMessage(authData.Login, verification, *req.Money) + if err != nil { + return receiver.middleWare.ErrorOld(ctx, err) + } + + return ctx.SendStatus(fiber.StatusOK) +} diff --git a/internal/interface/controller/http/wallet/route.go b/internal/interface/controller/http/wallet/route.go new file mode 100644 index 0000000..c679993 --- /dev/null +++ b/internal/interface/controller/http/wallet/route.go @@ -0,0 +1,13 @@ +package wallet + +import "github.com/gofiber/fiber/v2" + +func (receiver *WalletController) Register(router fiber.Router) { + router.Patch("/wallet", receiver.ChangeCurrency) + router.Post("/wallet", receiver.RequestMoney) + router.Post("/wallet/rspay", receiver.PostWalletRspay) +} + +func (receiver *WalletController) Name() string { + return "" +} diff --git a/internal/interface/repository/account.go b/internal/interface/repository/account.go index 75142cb..4e463fa 100644 --- a/internal/interface/repository/account.go +++ b/internal/interface/repository/account.go @@ -42,21 +42,6 @@ func NewAccountRepository(deps AccountRepositoryDeps) *AccountRepository { } } -func NewAccountRepository2(logger *zap.Logger, mongo *mongo.Collection) AccountRepository { - if logger == nil { - log.Panicln("logger is nil on ") - } - - if mongo == nil { - log.Panicln("mongodb is nil on ") - } - - return AccountRepository{ - logger: logger, - mongoDB: mongo, - } -} - func (receiver *AccountRepository) FindByUserID(ctx context.Context, id string) (*models.Account, errors.Error) { filter := bson.M{ fields.Account.UserID: id, diff --git a/internal/interface/repository/currency.go b/internal/interface/repository/currency.go index 354d4f9..e68b302 100644 --- a/internal/interface/repository/currency.go +++ b/internal/interface/repository/currency.go @@ -41,21 +41,6 @@ func NewCurrencyRepository(deps CurrencyRepositoryDeps) *CurrencyRepository { } } -func NewCurrencyRepository2(logger *zap.Logger, mongo *mongo.Collection) CurrencyRepository { - if logger == nil { - log.Panicln("logger is nil on ") - } - - if mongo == nil { - log.Panicln("mongodb is nil on ") - } - - return CurrencyRepository{ - logger: logger, - mongoDB: mongo, - } -} - func (receiver *CurrencyRepository) FindCurrenciesList(ctx context.Context, name string) (*models.CurrencyList, errors.Error) { filter := bson.M{ fields.Currency.Name: name, diff --git a/internal/interface/repository/history.go b/internal/interface/repository/history.go index 95a3508..282875b 100644 --- a/internal/interface/repository/history.go +++ b/internal/interface/repository/history.go @@ -45,21 +45,6 @@ func NewHistoryRepository(deps HistoryRepositoryDeps) *HistoryRepository { } } -func NewHistoryRepository2(logger *zap.Logger, mongo *mongo.Collection) HistoryRepository { - if logger == nil { - log.Panicln("logger is nil on ") - } - - if mongo == nil { - log.Panicln("mongodb is nil on ") - } - - return HistoryRepository{ - logger: logger, - mongoDB: mongo, - } -} - func (receiver *HistoryRepository) Insert(ctx context.Context, history *models.History) (*models.History, errors.Error) { result, err := receiver.mongoDB.InsertOne(ctx, history.Sanitize()) if err != nil { diff --git a/internal/models/payment.go b/internal/models/payment.go index d79c3a6..18ce9fe 100644 --- a/internal/models/payment.go +++ b/internal/models/payment.go @@ -31,12 +31,13 @@ type BankCard struct { type PaymentType string const ( - PaymentTypeBankCard PaymentType = "bankCard" - PaymentTypeTinkoff PaymentType = "tinkoffBank" - PaymentTypeSberPay PaymentType = "sberbank" - PaymentTypeYoomoney PaymentType = "yoomoney" - PaymentTypeSBP PaymentType = "sbp" - PaymentTypeSberB2B PaymentType = "b2bSberbank" + PaymentTypeBankCard PaymentType = "bankCard" + PaymentTypeTinkoff PaymentType = "tinkoffBank" + PaymentTypeSberPay PaymentType = "sberbank" + PaymentTypeYoomoney PaymentType = "yoomoney" + PaymentTypeSBP PaymentType = "sbp" + PaymentTypeSberB2B PaymentType = "b2bSberbank" + DefaultCurrency = "RUB" ) type PaymentEvent struct { @@ -45,6 +46,6 @@ type PaymentEvent struct { PaymentID string UserID string Currency string - Type string + Type string Amount int64 } diff --git a/internal/server/grpc.go b/internal/server/grpc.go index acf6f95..81943a5 100644 --- a/internal/server/grpc.go +++ b/internal/server/grpc.go @@ -65,7 +65,7 @@ func (receiver *GRPC) Stop(_ context.Context) error { return nil } -func (receiver *GRPC) Register(controllers *initialize.Controllers) *GRPC { +func (receiver *GRPC) Register(controllers *initialize.RpcControllers) *GRPC { payment_callback.RegisterPaymentCallbackServiceServer(receiver.grpc, controllers.PaymentController) customer.RegisterCustomerServiceServer(receiver.grpc, controllers.CustomerController) diff --git a/tests/integration/mail_test.go b/tests/integration/mail_test.go index 2cc6a3a..e301466 100644 --- a/tests/integration/mail_test.go +++ b/tests/integration/mail_test.go @@ -20,6 +20,7 @@ func TestSendMessage(t *testing.T) { Auth: &models.PlainAuth{Username: "kotilion.95@gmail.com", Password: "vWwbCSg4bf0p"}, FiberClient: fiber.AcquireClient(), Logger: zap.NewExample(), + MailAddress: "pashamullin2001@gmail.com", }) userEmail := "test@example.com" diff --git a/tests/integration/promo_ltv_test.go b/tests/integration/promo_ltv_test.go index 63f0e9d..e919c2a 100644 --- a/tests/integration/promo_ltv_test.go +++ b/tests/integration/promo_ltv_test.go @@ -42,7 +42,10 @@ func Test_PromoLTV(t *testing.T) { from := int64(0) to := int64(1714291104) - historyRepo := repository.NewHistoryRepository2(logger, mdb.Collection("histories")) + historyRepo := repository.NewHistoryRepository(repository.HistoryRepositoryDeps{ + Logger: logger, + MongoDB: mdb.Collection("histories"), + }) codewordData, err := codeword.GetAllPromoActivations(ctx, &codeword_rpc.Time{ From: from,