github.com/oinume/lekcije@v0.0.0-20231017100347-5b4c5eb6ab24/backend/infrastructure/mysql/user.go (about)

     1  package mysql
     2  
     3  import (
     4  	"context"
     5  	"database/sql"
     6  	"time"
     7  
     8  	"github.com/morikuni/failure"
     9  	"github.com/volatiletech/sqlboiler/v4/boil"
    10  	"github.com/volatiletech/sqlboiler/v4/queries"
    11  	"github.com/volatiletech/sqlboiler/v4/queries/qm"
    12  	"go.opentelemetry.io/otel"
    13  
    14  	"github.com/oinume/lekcije/backend/domain/config"
    15  	"github.com/oinume/lekcije/backend/domain/repository"
    16  	"github.com/oinume/lekcije/backend/errors"
    17  	"github.com/oinume/lekcije/backend/model2"
    18  )
    19  
    20  type userRepository struct {
    21  	db *sql.DB
    22  }
    23  
    24  func NewUserRepository(db *sql.DB) repository.User {
    25  	return &userRepository{
    26  		db: db,
    27  	}
    28  }
    29  
    30  func (r *userRepository) CreateWithExec(ctx context.Context, exec repository.Executor, user *model2.User) error {
    31  	return user.Insert(ctx, exec, boil.Infer())
    32  }
    33  
    34  func (r *userRepository) FindAllByEmailVerifiedIsTrue(ctx context.Context, notificationInterval int) ([]*model2.User, error) {
    35  	_, span := otel.Tracer(config.DefaultTracerName).Start(ctx, "userRepository.FindAllByEmailVerifiedIsTrue")
    36  	defer span.End()
    37  
    38  	query := `
    39  		SELECT u.* FROM (SELECT DISTINCT(user_id) FROM following_teacher) AS ft
    40  		INNER JOIN user AS u ON ft.user_id = u.id
    41  		INNER JOIN m_plan AS mp ON u.plan_id = mp.id
    42  		WHERE
    43  		  u.email_verified = 1
    44  		  AND mp.notification_interval = ?
    45  		ORDER BY u.open_notification_at DESC
    46  `
    47  	users := make([]*model2.User, 0, 1000)
    48  	if err := queries.Raw(query, notificationInterval).Bind(ctx, r.db, &users); err != nil {
    49  		if err == sql.ErrNoRows {
    50  			return nil, nil // Return empty slice without error
    51  		}
    52  		return nil, failure.Wrap(err)
    53  	}
    54  	return users, nil
    55  }
    56  
    57  func (r *userRepository) FindByAPIToken(ctx context.Context, apiToken string) (*model2.User, error) {
    58  	query := `
    59  		SELECT u.* FROM user AS u
    60  		INNER JOIN user_api_token AS uat ON u.id = uat.user_id
    61  		WHERE uat.token = ?
    62  		LIMIT 1
    63  	`
    64  	u := &model2.User{}
    65  	if err := queries.Raw(query, apiToken).Bind(ctx, r.db, u); err != nil {
    66  		if err == sql.ErrNoRows {
    67  			return nil, failure.Translate(err, errors.NotFound)
    68  		}
    69  		return nil, failure.Wrap(err)
    70  	}
    71  	// TODO: expose User.doAfterSelectHooks in template
    72  	return u, nil
    73  }
    74  
    75  func (r *userRepository) FindByEmail(ctx context.Context, email string) (*model2.User, error) {
    76  	return r.FindByEmailWithExec(ctx, r.db, email)
    77  }
    78  
    79  func (r *userRepository) FindByEmailWithExec(ctx context.Context, exec repository.Executor, email string) (*model2.User, error) {
    80  	return model2.Users(qm.Where("email = ?", email)).One(ctx, exec)
    81  }
    82  
    83  func (r *userRepository) FindByGoogleID(ctx context.Context, googleID string) (*model2.User, error) {
    84  	return r.findByGoogleIDWithExec(ctx, r.db, googleID)
    85  }
    86  
    87  func (r *userRepository) FindByGoogleIDWithExec(ctx context.Context, exec repository.Executor, googleID string) (*model2.User, error) {
    88  	return r.findByGoogleIDWithExec(ctx, exec, googleID)
    89  }
    90  
    91  func (r *userRepository) findByGoogleIDWithExec(ctx context.Context, exec repository.Executor, googleID string) (*model2.User, error) {
    92  	query := `
    93  		SELECT u.* FROM user AS u
    94  		INNER JOIN user_google AS ug ON u.id = ug.user_id
    95  		WHERE ug.google_id = ?
    96  		LIMIT 1
    97  	`
    98  	u := &model2.User{}
    99  	if err := queries.Raw(query, googleID).Bind(ctx, exec, u); err != nil {
   100  		if err == sql.ErrNoRows {
   101  			return nil, err
   102  		}
   103  		// TODO: Wrap error with morikuni/failure
   104  		return nil, errors.NewInternalError(
   105  			errors.WithError(err),
   106  			errors.WithResource(errors.NewResource("user_google", "google_id", googleID)),
   107  		)
   108  	}
   109  	// TODO: expose User.doAfterSelectHooks in template
   110  
   111  	return u, nil
   112  }
   113  
   114  func (r *userRepository) FindAllByEmailVerified(
   115  	ctx context.Context, notificationInterval int,
   116  ) ([]*model2.User, error) {
   117  	//TODO implement me
   118  	panic("implement me")
   119  }
   120  
   121  func (r *userRepository) UpdateEmail(ctx context.Context, id uint, email string) error {
   122  	const query = `UPDATE user SET email = ?, updated_at = NOW() WHERE id = ?`
   123  	_, err := queries.Raw(query, email, id).ExecContext(ctx, r.db)
   124  	if err != nil {
   125  		return errors.NewInternalError(
   126  			errors.WithError(err),
   127  			errors.WithMessage("Failed to update user email"),
   128  			errors.WithResource(errors.NewResourceWithEntries(
   129  				"user", []errors.ResourceEntry{
   130  					{Key: "id", Value: id}, {Key: "email", Value: email},
   131  				},
   132  			)),
   133  		)
   134  	}
   135  	return nil
   136  }
   137  
   138  func (r *userRepository) UpdateFollowedTeacherAt(ctx context.Context, id uint, time time.Time) error {
   139  	const query = "UPDATE user SET followed_teacher_at = ? WHERE id = ?"
   140  	_, err := queries.Raw(query, FormatDateTime(time), id).ExecContext(ctx, r.db)
   141  	if err != nil {
   142  		return errors.NewInternalError(
   143  			errors.WithError(err),
   144  			errors.WithMessage("Failed to update user followed_teacher_at"),
   145  		)
   146  	}
   147  	return nil
   148  }
   149  
   150  func (r *userRepository) UpdateOpenNotificationAt(ctx context.Context, id uint, time time.Time) error {
   151  	const query = "UPDATE user SET open_notification_at = ? WHERE id = ?"
   152  	_, err := queries.Raw(query, FormatDateTime(time), id).ExecContext(ctx, r.db)
   153  	if err != nil {
   154  		return errors.NewInternalError(
   155  			errors.WithError(err),
   156  			errors.WithMessage("Failed to update user open_notification_at"),
   157  		)
   158  	}
   159  	return nil
   160  }