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 }