customer/internal/interface/repository/history.go

378 lines
10 KiB
Go
Raw Normal View History

2023-05-23 10:52:27 +00:00
package repository
import (
"context"
"fmt"
2023-12-23 08:14:01 +00:00
"log"
"time"
2023-10-16 12:12:12 +00:00
"go.mongodb.org/mongo-driver/bson"
2023-05-23 10:52:27 +00:00
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
2023-05-23 15:24:52 +00:00
"go.mongodb.org/mongo-driver/mongo/options"
2023-05-23 10:52:27 +00:00
"go.uber.org/zap"
2024-02-02 12:15:03 +00:00
mongoWrapper "penahub.gitlab.yandexcloud.net/backend/penahub_common/mongo"
2023-05-23 10:52:27 +00:00
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/errors"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/fields"
2023-05-23 10:52:27 +00:00
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/models"
2023-11-05 06:37:57 +00:00
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/service/history"
2023-05-23 10:52:27 +00:00
)
type HistoryRepositoryDeps struct {
Logger *zap.Logger
MongoDB *mongo.Collection
}
type HistoryRepository struct {
logger *zap.Logger
mongoDB *mongo.Collection
}
2023-06-13 22:51:34 +00:00
func NewHistoryRepository(deps HistoryRepositoryDeps) *HistoryRepository {
2023-05-23 10:52:27 +00:00
if deps.Logger == nil {
log.Panicln("logger is nil on <NewHistoryRepository>")
}
if deps.MongoDB == nil {
log.Panicln("mongodb is nil on <NewHistoryRepository>")
}
return &HistoryRepository{
logger: deps.Logger,
mongoDB: deps.MongoDB,
}
}
2023-11-05 13:58:41 +00:00
func NewHistoryRepository2(logger *zap.Logger, mongo *mongo.Collection) HistoryRepository {
if logger == nil {
log.Panicln("logger is nil on <NewHistoryRepository>")
}
if mongo == nil {
log.Panicln("mongodb is nil on <NewHistoryRepository>")
}
return HistoryRepository{
logger: logger,
mongoDB: mongo,
}
}
2023-05-23 10:52:27 +00:00
func (receiver *HistoryRepository) Insert(ctx context.Context, history *models.History) (*models.History, errors.Error) {
2023-06-15 12:45:38 +00:00
result, err := receiver.mongoDB.InsertOne(ctx, history.Sanitize())
2023-05-23 10:52:27 +00:00
if err != nil {
receiver.logger.Error("failed to insert history on <Insert> of <HistoryRepository>",
zap.Any("history", history),
zap.Error(err),
)
return nil, errors.New(
fmt.Errorf("failed to insert history on <Insert> of <HistoryRepository>: %w", err),
errors.ErrInternalError,
)
}
insertedID := result.InsertedID.(primitive.ObjectID).Hex()
history.ID = insertedID
return history, nil
}
2023-11-05 06:37:57 +00:00
func (receiver *HistoryRepository) FindMany(ctx context.Context, dto *history.GetHistories) ([]models.History, errors.Error) {
2023-05-23 15:24:52 +00:00
findOptions := options.Find()
2023-09-14 10:02:32 +00:00
findOptions.SetSkip((dto.Pagination.Page - 1) * dto.Pagination.Limit)
findOptions.SetLimit(dto.Pagination.Limit)
2023-10-16 12:12:12 +00:00
findOptions.SetSort(bson.D{{
Key: "createdAt", Value: -1,
}})
2023-05-23 15:24:52 +00:00
histories, err := mongoWrapper.Find[models.History](ctx, &mongoWrapper.RequestSettings{
Driver: receiver.mongoDB,
Options: findOptions,
2023-09-14 10:02:32 +00:00
Filter: dto.BSON(),
2023-05-23 15:24:52 +00:00
})
if err != nil {
receiver.logger.Error("failed to find many histories on <FindMany> of <HistoryRepository>",
2023-09-14 10:02:32 +00:00
zap.Int64("page", dto.Pagination.Page),
zap.Int64("limit", dto.Pagination.Limit),
zap.Int64("skip", (dto.Pagination.Page-1)*dto.Pagination.Limit),
2023-05-23 15:24:52 +00:00
zap.Error(err),
)
return nil, errors.New(
fmt.Errorf("failed to find many histories on <FindMany> of <HistoryRepository>: %w", err),
errors.ErrInternalError,
)
}
return histories, nil
}
2023-11-05 06:37:57 +00:00
func (receiver *HistoryRepository) CountAll(ctx context.Context, dto *history.GetHistories) (int64, errors.Error) {
2023-09-14 23:01:53 +00:00
count, err := receiver.mongoDB.CountDocuments(ctx, dto.BSON())
2023-05-23 10:52:27 +00:00
if err != nil {
receiver.logger.Error("failed to count all documents on <CountAll> of <HistoryRepository>",
zap.Error(err),
)
return 0, errors.New(
fmt.Errorf("failed to count all documents on <CountAll> of <HistoryRepository>: %w", err),
errors.ErrInternalError,
)
}
return count, nil
}
2023-11-22 17:31:17 +00:00
// TODO:tests
// GetRecentTariffs method for processing a user request with data aggregation with a limit of 100 sorted in descending order.
2023-11-23 18:55:22 +00:00
func (receiver *HistoryRepository) GetRecentTariffs(ctx context.Context, userID string) ([]models.TariffID, errors.Error) {
matchStage := bson.D{
{Key: "$match", Value: bson.D{
{Key: fields.History.UserID, Value: userID},
{Key: fields.History.IsDeleted, Value: false},
{Key: fields.History.Type, Value: models.CustomerHistoryKeyPayCart},
}},
2023-11-22 17:31:17 +00:00
}
2023-11-22 21:30:59 +00:00
unwindStage := bson.D{
{Key: "$unwind", Value: bson.D{
{Key: "path", Value: "$rawDetails.tariffs"},
}},
}
groupStage := bson.D{
{Key: "$group", Value: bson.D{
{Key: "_id", Value: "$rawDetails.tariffs.id"},
}},
}
2023-11-23 18:55:22 +00:00
sortStage := bson.D{
{Key: "$sort", Value: bson.D{
{Key: "createdAt", Value: -1},
}},
}
limitStage := bson.D{
{Key: "$limit", Value: 100},
2023-11-22 17:31:17 +00:00
}
2023-11-22 18:07:18 +00:00
cursor, err := receiver.mongoDB.Aggregate(ctx, mongo.Pipeline{matchStage, unwindStage, sortStage, groupStage, limitStage})
2023-11-22 17:31:17 +00:00
if err != nil {
receiver.logger.Error("failed to get recent tariffs on <GetRecentTariffs> of <HistoryRepository>",
zap.String("userId", userID),
zap.Error(err),
)
return nil, errors.New(
fmt.Errorf("failed to get recent tariffs on <GetRecentTariffs> of <HistoryRepository>: %w", err),
errors.ErrInternalError,
)
}
2023-11-23 18:55:22 +00:00
var result []models.TariffID
2023-11-22 17:31:17 +00:00
if err := cursor.All(ctx, &result); err != nil {
receiver.logger.Error("failed to decode recent tariffs on <GetRecentTariffs> of <HistoryRepository>",
zap.String("userId", userID),
zap.Error(err),
)
return nil, errors.New(
fmt.Errorf("failed to decode recent tariffs on <GetRecentTariffs> of <HistoryRepository>: %w", err),
errors.ErrInternalError,
)
}
2023-11-23 18:55:22 +00:00
return result, nil
2023-11-22 17:31:17 +00:00
}
2023-11-25 18:28:26 +00:00
2023-12-01 11:27:44 +00:00
// TODO:tests.
2023-12-01 11:52:06 +00:00
func (receiver *HistoryRepository) GetHistoryByID(ctx context.Context, historyID string) (*models.ReportHistory, errors.Error) {
2023-11-25 18:28:26 +00:00
history := &models.ReportHistory{}
2024-02-02 09:56:33 +00:00
objID, err := primitive.ObjectIDFromHex(historyID)
if err != nil {
return nil, errors.New(fmt.Errorf("failed to convert history ID: %w", err), errors.ErrInternalError)
}
err = receiver.mongoDB.FindOne(ctx, bson.M{"_id": objID}).Decode(history)
2023-11-25 18:28:26 +00:00
if err != nil {
receiver.logger.Error(
"failed to find by id in <GetHistoryById> of <HistoryRepository>",
zap.String("historyID", historyID),
zap.Error(err),
)
if err == mongo.ErrNoDocuments {
return nil, errors.New(
fmt.Errorf("history not found with ID: %s", historyID),
errors.ErrNotFound,
)
}
return nil, errors.New(
fmt.Errorf("failed to find by id: %w", err),
errors.ErrInternalError,
)
}
return history, nil
}
2023-11-29 19:01:14 +00:00
2023-12-01 11:27:44 +00:00
// TODO:tests.
2023-11-29 19:01:14 +00:00
func (receiver *HistoryRepository) GetDocNumber(ctx context.Context, userID string) (map[string]int, errors.Error) {
findOptions := options.Find()
findOptions.SetSort(bson.D{{Key: "createdAt", Value: 1}})
filter := bson.M{
fields.History.UserID: userID,
}
cursor, err := receiver.mongoDB.Find(ctx, filter, findOptions)
if err != nil {
receiver.logger.Error("failed to get DocNumber list on <GetDocNumber> of <HistoryRepository>",
zap.String("userId", userID),
zap.Error(err),
)
return nil, errors.New(
fmt.Errorf("failed to get DocNumber list on <GetDocNumber> of <HistoryRepository>: %w", err),
errors.ErrInternalError,
)
}
2023-12-01 11:27:44 +00:00
defer func() {
if err := cursor.Close(ctx); err != nil {
receiver.logger.Error("failed to close cursor on <GetDocNumber> of <HistoryRepository>",
zap.String("userId", userID),
zap.Error(err),
)
}
}()
2023-11-29 19:01:14 +00:00
result := make(map[string]int)
var count int
for cursor.Next(ctx) {
var history models.History
if err := cursor.Decode(&history); err != nil {
receiver.logger.Error("failed to decode history on <GetDocNumber> of <HistoryRepository>",
zap.String("userId", userID),
zap.Error(err),
)
return nil, errors.New(
fmt.Errorf("failed to decode history on <GetDocNumber> of <HistoryRepository>: %w", err),
errors.ErrInternalError,
)
}
result[history.ID] = count
count++
}
if err := cursor.Err(); err != nil {
receiver.logger.Error("cursor error on <GetDocNumber> of <HistoryRepository>",
zap.String("userId", userID),
zap.Error(err),
)
return nil, errors.New(
fmt.Errorf("cursor error on <GetDocNumber> of <HistoryRepository>: %w", err),
errors.ErrInternalError,
)
}
return result, nil
}
2023-12-22 15:12:43 +00:00
func (receiver *HistoryRepository) CalculateCustomerLTV(ctx context.Context, from, to int64) (int64, errors.Error) {
timeFilter := bson.M{}
if from != 0 || to != 0 {
timeRange := bson.M{}
if from != 0 {
2023-12-22 22:27:45 +00:00
timeRange["$gte"] = time.Unix(from, 0).UTC().Format(time.RFC3339Nano)
2023-12-22 15:12:43 +00:00
}
if to != 0 {
2023-12-22 22:27:45 +00:00
timeRange["$lte"] = time.Unix(to, 0).UTC().Format(time.RFC3339Nano)
2023-12-22 15:12:43 +00:00
}
timeFilter["createdAt"] = timeRange
}
pipeline := mongo.Pipeline{
2023-12-23 08:14:01 +00:00
{{Key: "$match", Value: bson.M{"key": models.CustomerHistoryKeyPayCart, "isDeleted": false}}},
{{Key: "$match", Value: timeFilter}},
{{Key: "$group", Value: bson.M{
2023-12-22 15:12:43 +00:00
"_id": "$userId",
2023-12-22 22:27:45 +00:00
"firstPayment": bson.M{"$min": "$createdAt"},
"lastPayment": bson.M{"$max": "$createdAt"},
2023-12-22 15:12:43 +00:00
}}},
2023-12-23 08:14:01 +00:00
{{Key: "$project", Value: bson.M{
2023-12-22 15:12:43 +00:00
"lifeTimeInDays": bson.M{"$divide": []interface{}{
2023-12-22 22:27:45 +00:00
bson.M{"$subtract": []interface{}{bson.M{"$toDate": "$lastPayment"}, bson.M{"$toDate": "$firstPayment"}}},
2023-12-22 15:12:43 +00:00
86400000,
}},
}}},
2023-12-23 08:14:01 +00:00
{{Key: "$group", Value: bson.M{
2023-12-22 15:12:43 +00:00
"_id": nil,
"averageLTV": bson.M{"$avg": "$lifeTimeInDays"},
}}},
}
cursor, err := receiver.mongoDB.Aggregate(ctx, pipeline)
if err != nil {
receiver.logger.Error("failed to calculate customer LTV <CalculateCustomerLTV> of <HistoryRepository>",
zap.Error(err),
)
return 0, errors.New(
fmt.Errorf("failed to calculate customer LTV <CalculateCustomerLTV> of <HistoryRepository>: %w", err),
errors.ErrInternalError,
)
}
2023-12-23 08:14:01 +00:00
defer func() {
if err := cursor.Close(ctx); err != nil {
receiver.logger.Error("failed to close cursor", zap.Error(err))
}
}()
2023-12-22 15:12:43 +00:00
var results []struct{ AverageLTV float64 }
if err := cursor.All(ctx, &results); err != nil {
receiver.logger.Error("failed to getting result LTV <CalculateCustomerLTV> of <HistoryRepository>",
zap.Error(err),
)
return 0, errors.New(
fmt.Errorf("failed to getting result LTV <CalculateCustomerLTV> of <HistoryRepository>: %w", err),
errors.ErrInternalError,
)
}
if len(results) == 0 {
return 0, nil
}
2023-12-22 22:27:45 +00:00
averageLTV := int64(results[0].AverageLTV)
return averageLTV, nil
2023-12-22 15:12:43 +00:00
}
2024-04-15 09:27:00 +00:00
2024-04-16 17:02:44 +00:00
func (receiver *HistoryRepository) GetHistoryByListUsers(ctx context.Context, accounts []models.Account) (map[string][]models.History, error) {
historyMap := make(map[string][]models.History)
var IDs []string
for _, account := range accounts {
IDs = append(IDs, account.UserID)
}
filter := bson.M{"userId": bson.M{"$in": IDs}}
2024-04-15 09:27:00 +00:00
cursor, err := receiver.mongoDB.Find(ctx, filter)
if err != nil {
return nil, err
}
defer cursor.Close(ctx)
2024-04-16 17:02:44 +00:00
for cursor.Next(ctx) {
var history models.History
if err := cursor.Decode(&history); err != nil {
return nil, err
}
historyMap[history.UserID] = append(historyMap[history.UserID], history)
}
if err := cursor.Err(); err != nil {
2024-04-15 09:27:00 +00:00
return nil, err
}
2024-04-16 17:02:44 +00:00
return historyMap, nil
2024-04-15 09:27:00 +00:00
}