customer/internal/interface/controller/http/controllers.go

1026 lines
30 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 <SetVerificationStatus> of <AccountService>", 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, &currentTariff); 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 <Pay> of <CartService>", 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 <GetRecentTariffs> of <API2>",
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 <GetHistoryById> of <HistoryService>")
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 <GetHistoryById> of <HistoryService>",
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 <GetDocNumber> of <HistoryService>",
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 <GetHistoryById> of <HistoryService>",
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 <GetHistoryById> of <HistoryService>",
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 <GetHistoryById> of <HistoryService>",
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)
}