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) }