customer/internal/interface/swagger/api.2.go
2024-05-16 22:46:38 +03:00

956 lines
29 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 swagger
import (
"fmt"
"github.com/gofiber/fiber/v2"
"math"
"net/http"
"net/url"
"os"
codeword_rpc "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/proto/codeword"
"sync"
"time"
"github.com/labstack/echo/v4"
"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/echotools"
"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
}
var _ ServerInterface = (*API2)(nil)
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 echo.Context, status int, message string, rest ...any) error {
if len(rest) > 0 {
message = fmt.Sprintf(message, rest...)
}
api.logger.Error(message)
return ctx.JSON(status, models.ResponseErrorHTTP{
StatusCode: status,
Message: message,
})
}
func (api *API2) errorOld(ctx echo.Context, err errors.Error) error {
api.logger.Error("error:", zap.Error(err))
return errors.HTTP(ctx, err)
}
func (api *API2) noauth(ctx echo.Context) error {
return api.error(ctx, http.StatusUnauthorized, "failed to get jwt payload")
}
// Health
func (api *API2) GetHealth(ctx echo.Context) error {
return ctx.String(http.StatusOK, "OK")
}
// Account
func (api *API2) DeleteAccount(ctx echo.Context) error {
userID, ok := ctx.Get(models.AuthJWTDecodedUserIDKey).(string)
if !ok {
return api.noauth(ctx)
}
account, err := api.account.Remove(ctx.Request().Context(), userID)
if err != nil {
return api.errorOld(ctx, err)
}
return ctx.JSON(http.StatusOK, account)
}
func (api *API2) ChangeAccount(ctx echo.Context) error {
userID, ok := ctx.Get(models.AuthJWTDecodedUserIDKey).(string)
if !ok {
return api.noauth(ctx)
}
request, bindErr := echotools.Bind[models.Name](ctx)
if bindErr != nil {
return api.error(ctx, http.StatusBadRequest, "failed to bind json: %w", bindErr)
}
account, err := api.account.UpdateName(ctx.Request().Context(), userID, request)
if err != nil {
return api.errorOld(ctx, err)
}
return ctx.JSON(http.StatusOK, account)
}
func (api *API2) SetAccountVerificationStatus(ctx echo.Context, userID string) error {
request, bindErr := echotools.Bind[models.SetAccountStatus](ctx)
if bindErr != nil {
return api.error(ctx, http.StatusBadRequest, "failed to bind json: %w", bindErr)
}
account, err := api.account.SetStatus(ctx.Request().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.JSON(http.StatusOK, account)
}
func (api *API2) GetAccount(ctx echo.Context) error {
userID, ok := ctx.Get(models.AuthJWTDecodedUserIDKey).(string)
if !ok {
return api.noauth(ctx)
}
account, err := api.account.FindByUserID(ctx.Request().Context(), userID)
if err != nil {
return api.errorOld(ctx, err)
}
return ctx.JSON(http.StatusOK, account)
}
func (api *API2) AddAccount(ctx echo.Context) error {
userID, ok := ctx.Get(models.AuthJWTDecodedUserIDKey).(string)
if !ok {
return api.noauth(ctx)
}
var quizFrom, quizUser string
cookie, er := ctx.Request().Cookie("quizFrom")
if er == nil {
quizFrom = cookie.Value
}
cookie, er = ctx.Request().Cookie("quizUser")
if er == nil {
quizUser, er = url.QueryUnescape(cookie.Value)
if er == nil {
quizUser, er = api.encrypt.DecryptStr([]byte(quizUser))
}
}
account, err := api.account.FindByUserID(ctx.Request().Context(), userID)
if err != nil && err.Type() != errors.ErrNotFound {
return api.errorOld(ctx, err)
}
if account != nil {
return api.error(ctx, http.StatusBadRequest, "account exists")
}
user, err := api.clients.auth.GetUser(ctx.Request().Context(), userID)
if err != nil {
return api.errorOld(ctx, err)
}
account, err = api.account.Insert(ctx.Request().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.JSON(http.StatusOK, account)
}
func (api *API2) DeleteDirectAccount(ctx echo.Context, userID string) error {
account, err := api.account.Remove(ctx.Request().Context(), userID)
if err != nil {
return api.errorOld(ctx, err)
}
return ctx.JSON(http.StatusOK, account)
}
func (api *API2) GetDirectAccount(ctx echo.Context, userID string) error {
account, err := api.account.FindByUserID(ctx.Request().Context(), userID)
if err != nil {
return api.errorOld(ctx, err)
}
return ctx.JSON(http.StatusOK, account)
}
func (api *API2) PaginationAccounts(ctx echo.Context, params PaginationAccountsParams) error {
if params.Page == nil || params.Limit == nil {
return api.error(ctx, http.StatusInternalServerError, "default values missing for PaginationAccounts")
}
page := int64(math.Max(float64(*params.Page), 1))
limit := int64(math.Max(float64(*params.Limit), 1))
limit = int64(math.Min(float64(limit), float64(models.DefaultLimit)))
count, err := api.account.CountAll(ctx.Request().Context())
if err != nil {
return api.errorOld(ctx, err)
}
if count == 0 {
response := models.PaginationResponse[models.Account]{TotalPages: 0, Records: []models.Account{}}
return ctx.JSON(http.StatusOK, response)
}
totalPages := int64(math.Ceil(float64(count) / float64(limit)))
accounts, err := api.account.FindMany(ctx.Request().Context(), page, limit)
if err != nil {
return api.errorOld(ctx, err)
}
response := models.PaginationResponse[models.Account]{
TotalPages: totalPages,
Records: accounts,
}
return ctx.JSON(http.StatusOK, response)
}
// Cart
func (api *API2) RemoveFromCart(ctx echo.Context, params RemoveFromCartParams) error {
userID, ok := ctx.Get(models.AuthJWTDecodedUserIDKey).(string)
if !ok {
return api.noauth(ctx)
}
if validate.IsStringEmpty(params.Id) {
return api.error(ctx, http.StatusBadRequest, "empty item id")
}
cartItems, err := api.account.RemoveItemFromCart(ctx.Request().Context(), userID, params.Id)
if err != nil {
return api.errorOld(ctx, err)
}
return ctx.JSON(http.StatusOK, cartItems)
}
func (api *API2) Add2cart(ctx echo.Context, params Add2cartParams) error {
userID, ok := ctx.Get(models.AuthJWTDecodedUserIDKey).(string)
if !ok {
return api.noauth(ctx)
}
token, ok := ctx.Get(models.AuthJWTDecodedAccessTokenKey).(string)
if !ok {
return api.noauth(ctx)
}
if validate.IsStringEmpty(params.Id) {
return api.error(ctx, http.StatusBadRequest, "empty item id")
}
tariffID := params.Id
tariff, err := api.clients.hubadmin.GetTariff(ctx.Request().Context(), token, tariffID)
if err != nil {
return api.errorOld(ctx, err)
}
if tariff == nil {
return api.error(ctx, http.StatusNotFound, "tariff not found")
}
cartItems, err := api.account.AddItemToCart(ctx.Request().Context(), userID, tariffID)
if err != nil {
return api.errorOld(ctx, err)
}
return ctx.JSON(http.StatusOK, cartItems)
}
func (api *API2) PayCart(ctx echo.Context) error {
userID, ok := ctx.Get(models.AuthJWTDecodedUserIDKey).(string)
if !ok {
return api.noauth(ctx)
}
accessToken, ok := ctx.Get(models.AuthJWTDecodedAccessTokenKey).(string)
if !ok {
return api.noauth(ctx)
}
account, err := api.account.FindByUserID(ctx.Request().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.Request().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.Request().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, http.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.Request().Context(), &discount.GetDiscountByIDRequest{
ID: applied.ID,
})
if err != nil {
return api.error(ctx, http.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.Request().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.Request().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.Request().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.Request().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.Request().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, http.StatusInternalServerError, "failed to send tariffs to broker")
}
if _, err := api.account.ClearCart(ctx.Request().Context(), account.UserID); err != nil {
return api.errorOld(ctx, err)
}
updatedAccount.Cart = []string{}
return ctx.JSON(http.StatusOK, updatedAccount)
}
// Currency
func (api *API2) GetCurrencies(ctx echo.Context) error {
currencyList, err := api.currency.FindCurrenciesList(ctx.Request().Context(), models.DefaultCurrencyListName)
if err != nil && err.Type() != errors.ErrNotFound {
return api.errorOld(ctx, err)
}
if err != nil && err.Type() == errors.ErrNotFound {
return ctx.JSON(http.StatusOK, []string{})
}
return ctx.JSON(http.StatusOK, currencyList)
}
func (api *API2) UpdateCurrencies(ctx echo.Context) error {
currenciesPtr, bindErr := echotools.Bind[[]string](ctx)
if bindErr != nil {
return api.error(ctx, http.StatusBadRequest, "faild to bind currencies")
}
currencies := *currenciesPtr
currencyList, err := api.currency.ReplaceCurrencies(ctx.Request().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.Request().Context(), &models.CurrencyList{
Name: models.DefaultCurrencyListName,
Currencies: currencies,
})
if err != nil && err.Type() != errors.ErrNotFound {
return api.errorOld(ctx, err)
}
return ctx.JSON(http.StatusOK, newCurrencyList.Currencies)
}
return ctx.JSON(http.StatusOK, currencyList.Currencies)
}
// History
func (api *API2) GetHistory(ctx echo.Context, params GetHistoryParams) error {
var userID string
if params.AccountID != nil && *params.AccountID != "" {
userID = *params.AccountID
} else {
userID, _ = ctx.Get(models.AuthJWTDecodedUserIDKey).(string)
}
dto := &history.GetHistories{
UserID: userID,
Type: params.Type,
Pagination: &models.Pagination{
Page: int64(*params.Page),
Limit: int64(*params.Limit),
},
}
count, err := api.history.CountAll(ctx.Request().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.JSON(http.StatusOK, returnHistories)
}
totalPages := int64(math.Ceil(float64(count) / float64(dto.Pagination.Limit)))
histories, err := api.history.FindMany(ctx.Request().Context(), dto)
if err != nil {
return api.errorOld(ctx, err)
}
returnHistories := models.PaginationResponse[models.History]{
TotalPages: totalPages,
Records: histories,
}
return ctx.JSON(http.StatusOK, returnHistories)
}
// Wallet
func (api *API2) RequestMoney(ctx echo.Context) error {
userID, ok := ctx.Get(models.AuthJWTDecodedUserIDKey).(string)
if !ok {
return api.noauth(ctx)
}
request, bindErr := echotools.Bind[models.GetPaymentLinkBody](ctx)
if bindErr != nil {
return api.error(ctx, http.StatusBadRequest, "faild to bind payment link")
}
if err := utils.ValidateGetPaymentLinkBody(request); err != nil {
return api.errorOld(ctx, err)
}
link, err := api.GetPaymentLink(ctx.Request().Context(), &models.GetPaymentLinkRequest{
Body: request,
UserID: userID,
ClientIP: ctx.RealIP(),
})
if err != nil {
return api.errorOld(ctx, err)
}
return ctx.JSON(http.StatusOK, &models.GetPaymentLinkResponse{Link: link})
}
func (api *API2) ChangeCurrency(ctx echo.Context) error {
userID, ok := ctx.Get(models.AuthJWTDecodedUserIDKey).(string)
if !ok {
return api.noauth(ctx)
}
request, bindErr := echotools.Bind[models.ChangeCurrency](ctx)
if bindErr != nil {
return api.error(ctx, http.StatusBadRequest, "faild to bind currency")
}
if validate.IsStringEmpty(request.Currency) {
return api.error(ctx, http.StatusBadRequest, "empty currency")
}
currency := request.Currency
account, err := api.account.FindByUserID(ctx.Request().Context(), userID)
if err != nil {
return api.errorOld(ctx, err)
}
cash, err := api.clients.currency.Translate(ctx.Request().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.Request().Context(), account.UserID, &models.Wallet{
Cash: cash,
Currency: currency,
Money: account.Wallet.Money,
})
if err != nil {
return api.errorOld(ctx, err)
}
return ctx.JSON(http.StatusOK, updatedAccount)
}
func (api *API2) CalculateLTV(ctx echo.Context) error {
var req CalculateLTVJSONBody
if err := ctx.Bind(&req); err != nil {
api.logger.Error("failed to bind request", zap.Error(err))
return api.error(ctx, http.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, http.StatusBadRequest, "From timestamp must be less than To timestamp unless To is 0")
}
ltv, err := api.history.CalculateCustomerLTV(ctx.Request().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.JSON(http.StatusOK, response)
}
func (api *API2) GetRecentTariffs(ctx echo.Context) error {
userID, ok := ctx.Get(models.AuthJWTDecodedUserIDKey).(string)
if !ok {
api.logger.Error("failed to convert jwt payload to string on <GetRecentTariffs> of <API2>")
return api.error(ctx, http.StatusBadRequest, "failed to convert jwt payload to string")
}
if userID == "" {
api.logger.Error("user id is missing in <GetRecentTariffs> of <API2>")
return api.error(ctx, http.StatusBadRequest, "user id is missing")
}
tariffs, err := api.history.GetRecentTariffs(ctx.Request().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.JSON(http.StatusOK, tariffs)
}
func (api *API2) SendReport(ctx echo.Context) error {
var req SendReportJSONBody
if err := ctx.Bind(&req); err != nil {
api.logger.Error("failed to bind request", zap.Error(err))
return api.error(ctx, http.StatusBadRequest, "failed to bind request")
}
if req.Id == "" {
api.logger.Error("history id is missing in <GetHistoryById> of <HistoryService>")
return api.error(ctx, http.StatusBadRequest, "history id is missing")
}
tariffs, err := api.history.GetHistoryByID(ctx.Request().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, http.StatusBadRequest, "invalid history record key")
}
historyMap, err := api.history.GetDocNumber(ctx.Request().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.Request().Header.Get("Authorization")
fmt.Println("HEADERS", ctx.Request().Header)
verifuser, err := api.clients.verify.GetVerification(ctx.Request().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, http.StatusBadRequest, "verification not accepted")
}
account, err := api.account.FindByUserID(ctx.Request().Context(), tariffs.UserID)
if err != nil {
return api.errorOld(ctx, err)
}
authuser, err := api.clients.auth.GetUser(ctx.Request().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, http.StatusInternalServerError, "failed to read file")
}
for _, tariff := range tariffs.RawDetails.Tariffs {
totalAmount := uint64(0)
for _, privilege := range tariff.Privileges {
totalAmount += privilege.Amount
}
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,
Amount: fmt.Sprint(totalAmount),
Price: fmt.Sprint(tariffs.RawDetails.Price),
Sum: fmt.Sprint(tariffs.RawDetails.Price),
}
err = api.clients.template.SendData(ctx.Request().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.NoContent(http.StatusOK)
}
func (api *API2) PostWalletRspay(ctx echo.Context) error {
userID, ok := ctx.Get(models.AuthJWTDecodedUserIDKey).(string)
if !ok {
return api.noauth(ctx)
}
var req PostWalletRspayJSONBody
if err := ctx.Bind(&req); err != nil {
api.logger.Error("failed to bind request", zap.Error(err))
return api.error(ctx, http.StatusBadRequest, "failed to bind request")
}
user, err := api.account.FindByUserID(ctx.Request().Context(), userID)
if err != nil {
return api.errorOld(ctx, err)
}
if user.Status != models.AccountStatusNko && user.Status != models.AccountStatusOrg {
return api.error(ctx, http.StatusForbidden, "not allowed for non organizations")
}
token := ctx.Request().Header.Get("Authorization")
fmt.Println("HEADERS", ctx.Request().Header)
verification, err := api.clients.verify.GetVerification(ctx.Request().Context(), token, userID)
if err == errors.ErrNotFound {
return api.error(ctx, http.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, http.StatusForbidden, "not enough verification files")
}
authData, err := api.clients.auth.GetUser(ctx.Request().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.NoContent(http.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 echo.Context) error {
var req QuizLogoStatJSONRequestBody
if err := ctx.Bind(&req); err != nil {
api.logger.Error("failed to bind request", zap.Error(err))
return api.error(ctx, http.StatusBadRequest, "failed to bind request")
}
result, err := api.account.QuizLogoStat(ctx.Request().Context(), repository.QuizLogoStatDeps{
Page: req.Page,
Limit: req.Limit,
From: req.From,
To: req.To,
})
if err != nil {
return api.error(ctx, http.StatusInternalServerError, fmt.Sprint("failed getting quiz logo stat", err.Error()))
}
return ctx.JSON(http.StatusOK, result)
}
func (api *API2) PromocodeLTV(ctx echo.Context) error {
var req PromocodeLTVJSONRequestBody
if err := ctx.Bind(&req); err != nil {
api.logger.Error("failed to bind request", zap.Error(err))
return api.error(ctx, http.StatusBadRequest, "failed to bind request")
}
// получаем мапу вида [promoID] = []{userid,timeActivate}
// отдаются только первые использованые на аккаунте промокоды, соответсвенно подсчет идет сугубо по ним
// если в запросе время различается с временем активации - если меньше, то учитывается только после применения
// если больше, то учитывается только с начала переданного from
codewordData, err := api.clients.codeword.GetAllPromoActivations(ctx.Request().Context(), &codeword_rpc.Time{
To: int64(req.To),
From: int64(req.From),
})
if err != nil {
return api.error(ctx, http.StatusInternalServerError, fmt.Sprint("failed getting codeword data", err.Error()))
}
userSumMap, er := api.history.GetPayUsersPromoHistory(ctx.Request().Context(), codewordData, int64(req.From), int64(req.To))
if er != nil {
return api.error(ctx, http.StatusInternalServerError, fmt.Sprint("failed clculate 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.JSON(http.StatusOK, resp)
}