generated from PenaSide/GolangTemplate
1026 lines
30 KiB
Go
1026 lines
30 KiB
Go
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, ¤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 <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)
|
||
}
|