customer/internal/interface/repository/account.go

455 lines
12 KiB
Go

package repository
import (
"context"
"fmt"
"log"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"go.uber.org/zap"
mongoWrapper "penahub.gitlab.yandexcloud.net/backend/penahub_common/mongo"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/errors"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/fields"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/models"
)
type AccountRepositoryDeps struct {
Logger *zap.Logger
MongoDB *mongo.Collection
}
type AccountRepository struct {
logger *zap.Logger
mongoDB *mongo.Collection
}
func NewAccountRepository(deps AccountRepositoryDeps) *AccountRepository {
if deps.Logger == nil {
log.Panicln("logger is nil on <NewAccountRepository>")
}
if deps.MongoDB == nil {
log.Panicln("mongodb is nil on <NewAccountRepository>")
}
return &AccountRepository{
logger: deps.Logger,
mongoDB: deps.MongoDB,
}
}
func NewAccountRepository2(logger *zap.Logger, mongo *mongo.Collection) AccountRepository {
if logger == nil {
log.Panicln("logger is nil on <NewAccountRepository>")
}
if mongo == nil {
log.Panicln("mongodb is nil on <NewAccountRepository>")
}
return AccountRepository{
logger: logger,
mongoDB: mongo,
}
}
func (receiver *AccountRepository) FindByUserID(ctx context.Context, id string) (*models.Account, errors.Error) {
filter := bson.M{
fields.Account.UserID: id,
fields.Account.IsDeleted: false,
}
account, err := mongoWrapper.FindOne[models.Account](ctx, &mongoWrapper.RequestSettings{
Driver: receiver.mongoDB,
Filter: filter,
})
if err != nil {
receiver.logger.Error("failed to find account by userID on <FindByUserID> of <AccountRepository>",
zap.String("id", id),
zap.Error(err),
)
findError := errors.New(
fmt.Errorf("failed to find account with <%s> on <FindByUserID> of <AccountRepository>: %w", id, err),
errors.ErrInternalError,
)
if err == mongo.ErrNoDocuments {
findError.SetType(errors.ErrNotFound)
}
return nil, findError
}
return account, nil
}
func (receiver *AccountRepository) FindMany(ctx context.Context, page, limit int64) ([]models.Account, errors.Error) {
filter := bson.M{fields.Account.IsDeleted: false}
findOptions := options.Find()
skip := (page - 1) * limit
findOptions.SetSkip(skip)
findOptions.SetLimit(limit)
accounts, err := mongoWrapper.Find[models.Account](ctx, &mongoWrapper.RequestSettings{
Driver: receiver.mongoDB,
Options: findOptions,
Filter: filter,
})
if err != nil {
receiver.logger.Error("failed to find many accounts on <FindMany> of <AccountRepository>",
zap.Int64("page", page),
zap.Int64("limit", limit),
zap.Int64("skip", skip),
zap.Error(err),
)
return nil, errors.New(
fmt.Errorf("failed to find many accounts on <FindMany> of <AccountRepository>: %w", err),
errors.ErrInternalError,
)
}
return accounts, nil
}
func (receiver *AccountRepository) Insert(ctx context.Context, account *models.Account) (*models.Account, errors.Error) {
result, err := receiver.mongoDB.InsertOne(ctx, account.Sanitize())
if err != nil {
receiver.logger.Error("failed to insert account on <Insert> of <AccountRepository>",
zap.Any("account", account),
zap.Error(err),
)
return nil, errors.New(
fmt.Errorf("failed to insert account on <Insert> of <AccountRepository>: %w", err),
errors.ErrInternalError,
)
}
insertedID := result.InsertedID.(primitive.ObjectID).Hex()
account.ID = insertedID
return account, nil
}
func (receiver *AccountRepository) Remove(ctx context.Context, id string) (*models.Account, errors.Error) {
account := models.Account{}
options := options.FindOneAndUpdate().SetReturnDocument(options.After)
filter := bson.M{
fields.Account.UserID: id,
fields.Account.IsDeleted: false,
}
update := bson.M{"$set": bson.M{
fields.Account.IsDeleted: true,
fields.Account.DeletedAt: time.Now(),
}}
if err := receiver.mongoDB.FindOneAndUpdate(ctx, filter, update, options).Decode(&account); err != nil {
receiver.logger.Error("failed to set 'deleted=true' on <Delete> of <AccountRepository>",
zap.String("id", id),
zap.Error(err),
)
removeErr := errors.New(
fmt.Errorf("failed to remove account with <%s> on <Remove> of <AccountRepository>: %w", id, err),
errors.ErrInternalError,
)
if err == mongo.ErrNoDocuments {
removeErr.SetType(errors.ErrNotFound)
}
return nil, removeErr
}
return &account, nil
}
func (receiver *AccountRepository) CountAll(ctx context.Context) (int64, errors.Error) {
count, err := receiver.mongoDB.CountDocuments(ctx, bson.M{fields.Account.IsDeleted: false})
if err != nil {
receiver.logger.Error("failed to count all documents on <CountAll> of <AccountRepository>", zap.Error(err))
return 0, errors.New(
fmt.Errorf("failed to count all documents on <CountAll> of <AccountRepository>: %w", err),
errors.ErrInternalError,
)
}
return count, nil
}
func (receiver *AccountRepository) AddItemToCart(ctx context.Context, userID, itemID string) (*models.Account, errors.Error) {
account := models.Account{}
options := options.FindOneAndUpdate().SetReturnDocument(options.After)
filter := bson.M{
fields.Account.UserID: userID,
fields.Account.IsDeleted: false,
}
update := bson.M{
"$addToSet": bson.M{fields.Account.Cart: itemID},
"$set": bson.M{fields.Account.UpdatedAt: time.Now()},
}
if err := receiver.mongoDB.FindOneAndUpdate(ctx, filter, update, options).Decode(&account); err != nil {
receiver.logger.Error("failed to add item on <AddItemToCart> of <AccountRepository>",
zap.String("userID", userID),
zap.String("itemID", itemID),
zap.Error(err),
)
removeErr := errors.New(
fmt.Errorf("failed to add item <%s> account with <%s> on <AddItemToCart> of <AccountRepository>: %w", itemID, userID, err),
errors.ErrInternalError,
)
if err == mongo.ErrNoDocuments {
removeErr.SetType(errors.ErrNotFound)
}
return nil, removeErr
}
return &account, nil
}
func (receiver *AccountRepository) RemoveItemFromCart(ctx context.Context, userID, itemID string) (*models.Account, errors.Error) {
account := models.Account{}
options := options.FindOneAndUpdate().SetReturnDocument(options.After)
filter := bson.M{
fields.Account.UserID: userID,
fields.Account.IsDeleted: false,
}
update := bson.M{
"$pull": bson.M{fields.Account.Cart: itemID},
"$set": bson.M{fields.Account.UpdatedAt: time.Now()},
}
if err := receiver.mongoDB.FindOneAndUpdate(ctx, filter, update, options).Decode(&account); err != nil {
receiver.logger.Error("failed to add item on <AddItemToCart> of <AccountRepository>",
zap.String("userID", userID),
zap.String("itemID", itemID),
zap.Error(err),
)
removeErr := errors.New(
fmt.Errorf("failed to add item <%s> account with <%s> on <AddItemToCart> of <AccountRepository>: %w", itemID, userID, err),
errors.ErrInternalError,
)
if err == mongo.ErrNoDocuments {
removeErr.SetType(errors.ErrNotFound)
}
return nil, removeErr
}
return &account, nil
}
func (receiver *AccountRepository) ChangeWallet(ctx context.Context, userID string, wallet *models.Wallet) (*models.Account, errors.Error) {
account := models.Account{}
options := options.FindOneAndUpdate().SetReturnDocument(options.After)
filter := bson.M{
fields.Account.UserID: userID,
fields.Account.IsDeleted: false,
}
update := bson.M{"$set": bson.M{
fields.Account.Wallet: wallet,
fields.Account.UpdatedAt: time.Now(),
}}
if err := receiver.mongoDB.FindOneAndUpdate(ctx, filter, update, options).Decode(&account); err != nil {
receiver.logger.Error("failed to change wallet on <ChangeWallet> of <AccountRepository>",
zap.Error(err),
zap.String("userID", userID),
zap.Any("wallet", wallet),
)
removeErr := errors.New(
fmt.Errorf("failed to change wallet of account <%s> on <ChangeWallet> of <AccountRepository>: %w", userID, err),
errors.ErrInternalError,
)
if err == mongo.ErrNoDocuments {
removeErr.SetType(errors.ErrNotFound)
}
return nil, removeErr
}
return &account, nil
}
func (receiver *AccountRepository) ClearCart(ctx context.Context, userID string) (*models.Account, errors.Error) {
account := models.Account{}
options := options.FindOneAndUpdate().SetReturnDocument(options.After)
filter := bson.M{
fields.Account.UserID: userID,
fields.Account.IsDeleted: false,
}
update := bson.M{"$set": bson.M{
fields.Account.Cart: []string{},
fields.Account.UpdatedAt: time.Now(),
}}
if err := receiver.mongoDB.FindOneAndUpdate(ctx, filter, update, options).Decode(&account); err != nil {
receiver.logger.Error("failed to clear cart on <ClearCart> of <AccountRepository>",
zap.String("userID", userID),
zap.Error(err),
)
removeErr := errors.New(
fmt.Errorf("failed to clear cart of account <%s> on <ClearCart> of <AccountRepository>: %w", userID, err),
errors.ErrInternalError,
)
if err == mongo.ErrNoDocuments {
removeErr.SetType(errors.ErrNotFound)
}
return nil, removeErr
}
return &account, nil
}
func (receiver *AccountRepository) SetStatus(ctx context.Context, userID string, status models.AccountStatus) (*models.Account, errors.Error) {
account := models.Account{}
options := options.FindOneAndUpdate().SetReturnDocument(options.After)
filter := bson.M{
fields.Account.UserID: userID,
fields.Account.IsDeleted: false,
}
update := bson.M{"$set": bson.M{
fields.Account.Status: status,
fields.Account.UpdatedAt: time.Now(),
}}
if err := receiver.mongoDB.FindOneAndUpdate(ctx, filter, update, options).Decode(&account); err != nil {
receiver.logger.Error("failed to set status on <SetStatus> of <AccountRepository>",
zap.Error(err),
zap.String("userID", userID),
zap.String("status", string(status)),
)
removeErr := errors.New(
fmt.Errorf("failed to set status <%s> to account <%s> on <SetStatus> of <AccountRepository>: %w", status, userID, err),
errors.ErrInternalError,
)
if err == mongo.ErrNoDocuments {
removeErr.SetType(errors.ErrNotFound)
}
return nil, removeErr
}
return &account, nil
}
func (receiver *AccountRepository) UpdateName(ctx context.Context, userID string, name *models.Name) (*models.Account, errors.Error) {
account := models.Account{}
options := options.FindOneAndUpdate().SetReturnDocument(options.After)
filter := bson.M{
fields.Account.UserID: userID,
fields.Account.IsDeleted: false,
}
update := bson.M{"$set": bson.M{
fields.Account.Name: name,
fields.Account.UpdatedAt: time.Now(),
}}
if err := receiver.mongoDB.FindOneAndUpdate(ctx, filter, update, options).Decode(&account); err != nil {
receiver.logger.Error("failed to change name on <UpdateName> of <AccountRepository>",
zap.Error(err),
zap.String("userID", userID),
zap.Any("name", name),
)
removeErr := errors.New(
fmt.Errorf("failed to change name of account <%s> on <UpdateName> of <AccountRepository>: %w", userID, err),
errors.ErrInternalError,
)
if err == mongo.ErrNoDocuments {
removeErr.SetType(errors.ErrNotFound)
}
return nil, removeErr
}
return &account, nil
}
type QuizLogoStatDeps struct {
From *int
Limit *int
Page *int
To *int
}
func (receiver *AccountRepository) QuizLogoStat(ctx context.Context, req QuizLogoStatDeps) ([]models.Account, int64, error) {
filter := bson.M{}
filter2 := bson.M{}
if req.From != nil || req.To != nil {
timeRange := bson.M{}
if *req.From != 0 {
timeRange["$gte"] = time.Unix(int64(*req.From), 0).UTC().Format(time.RFC3339Nano)
}
if *req.To != 0 {
timeRange["$lte"] = time.Unix(int64(*req.To), 0).UTC().Format(time.RFC3339Nano)
}
filter["createdAt"] = timeRange
}
filter["quizFrom"] = bson.M{"$ne": ""}
filter["partner"] = bson.M{"$ne": ""}
filter2["quizFrom"] = bson.M{"$ne": ""}
filter2["partner"] = bson.M{"$ne": ""}
options := options.Find()
if req.Page != nil && req.Limit != nil {
options.SetSkip(int64(*req.Page * *req.Limit))
options.SetLimit(int64(*req.Limit))
}
count, err := receiver.mongoDB.CountDocuments(ctx, filter2)
if err != nil {
return nil, 0, err
}
cursor, err := receiver.mongoDB.Find(ctx, filter, options)
if err != nil {
return nil, 0, err
}
defer cursor.Close(ctx)
var accounts []models.Account
if err := cursor.All(ctx, &accounts); err != nil {
return nil, 0, err
}
return accounts, count, nil
}