all repos

onasty @ 09a22a834f95a9c5a2c710c084cdb039c29283fb

a one-time notes service

onasty/internal/store/psql/userepo/userepo.go (view raw)

Smirnov Oleksandr Smirnov Oleksandr
ss2316544@gmail.com
fix: typos (#22)..., 1 year ago
1
package userepo
2
3
import (
4
	"context"
5
	"errors"
6
7
	"github.com/gofrs/uuid/v5"
8
	"github.com/henvic/pgq"
9
	"github.com/jackc/pgx/v5"
10
	"github.com/olexsmir/onasty/internal/dtos"
11
	"github.com/olexsmir/onasty/internal/models"
12
	"github.com/olexsmir/onasty/internal/store/psqlutil"
13
)
14
15
type UserStorer interface {
16
	Create(ctx context.Context, inp dtos.CreateUserDTO) (uuid.UUID, error)
17
18
	// GetUserByCredentials returns user by email and password
19
	// the password should be hashed
20
	GetUserByCredentials(ctx context.Context, email, password string) (dtos.UserDTO, error)
21
22
	GetUserIDByEmail(ctx context.Context, email string) (uuid.UUID, error)
23
	MarkUserAsActivated(ctx context.Context, id uuid.UUID) error
24
25
	// ChangePassword changes user password from oldPassword to newPassword
26
	// and oldPassword and newPassword should be hashed
27
	ChangePassword(ctx context.Context, userID uuid.UUID, oldPassword, newPassword string) error
28
29
	// SetPassword sets new password for user by their id
30
	// password should be hashed
31
	SetPassword(ctx context.Context, userID uuid.UUID, newPassword string) error
32
33
	CheckIfUserExists(ctx context.Context, userID uuid.UUID) (bool, error)
34
	CheckIfUserIsActivated(ctx context.Context, userID uuid.UUID) (bool, error)
35
}
36
37
var _ UserStorer = (*UserRepo)(nil)
38
39
type UserRepo struct {
40
	db *psqlutil.DB
41
}
42
43
func New(db *psqlutil.DB) *UserRepo {
44
	return &UserRepo{
45
		db: db,
46
	}
47
}
48
49
func (r *UserRepo) Create(ctx context.Context, inp dtos.CreateUserDTO) (uuid.UUID, error) {
50
	query, args, err := pgq.
51
		Insert("users").
52
		Columns("username", "email", "password", "created_at", "last_login_at").
53
		Values(inp.Username, inp.Email, inp.Password, inp.CreatedAt, inp.LastLoginAt).
54
		Returning("id").
55
		SQL()
56
	if err != nil {
57
		return uuid.UUID{}, err
58
	}
59
60
	var id uuid.UUID
61
	err = r.db.QueryRow(ctx, query, args...).Scan(&id)
62
63
	// FIXME: somehow this does return errors but i can't errors.Is them in api layer
64
	if psqlutil.IsDuplicateErr(err, "users_username_key") {
65
		return uuid.UUID{}, models.ErrUsernameIsAlreadyInUse
66
	}
67
68
	if psqlutil.IsDuplicateErr(err, "users_email_key") {
69
		return uuid.UUID{}, models.ErrUserEmailIsAlreadyInUse
70
	}
71
72
	return id, err
73
}
74
75
func (r *UserRepo) GetUserByCredentials(
76
	ctx context.Context,
77
	email, password string,
78
) (dtos.UserDTO, error) {
79
	query, args, err := pgq.
80
		Select("id", "username", "email", "password", "activated", "created_at", "last_login_at").
81
		From("users").
82
		Where(pgq.Eq{
83
			"email":    email,
84
			"password": password,
85
		}).
86
		SQL()
87
	if err != nil {
88
		return dtos.UserDTO{}, err
89
	}
90
91
	var user dtos.UserDTO
92
	err = r.db.QueryRow(ctx, query, args...).
93
		Scan(&user.ID, &user.Username, &user.Email, &user.Password, &user.Activated, &user.CreatedAt, &user.LastLoginAt)
94
	if errors.Is(err, pgx.ErrNoRows) {
95
		return dtos.UserDTO{}, models.ErrUserNotFound
96
	}
97
98
	return user, err
99
}
100
101
func (r *UserRepo) GetUserIDByEmail(ctx context.Context, email string) (uuid.UUID, error) {
102
	query, args, err := pgq.
103
		Select("id").
104
		From("users").
105
		Where(pgq.Eq{"email": email}).
106
		SQL()
107
	if err != nil {
108
		return uuid.Nil, err
109
	}
110
111
	var id uuid.UUID
112
	err = r.db.QueryRow(ctx, query, args...).Scan(&id)
113
	if errors.Is(err, pgx.ErrNoRows) {
114
		return uuid.Nil, models.ErrUserNotFound
115
	}
116
117
	return id, err
118
}
119
120
func (r *UserRepo) MarkUserAsActivated(ctx context.Context, id uuid.UUID) error {
121
	query, args, err := pgq.
122
		Update("users").
123
		Set("activated ", true).
124
		Where(pgq.Eq{"id": id.String()}).
125
		SQL()
126
	if err != nil {
127
		return err
128
	}
129
130
	_, err = r.db.Exec(ctx, query, args...)
131
	return err
132
}
133
134
func (r *UserRepo) ChangePassword(
135
	ctx context.Context,
136
	userID uuid.UUID,
137
	oldPass, newPass string,
138
) error {
139
	query, args, err := pgq.
140
		Update("users").
141
		Set("password", newPass).
142
		Where(pgq.Eq{
143
			"id":       userID.String(),
144
			"password": oldPass,
145
		}).
146
		SQL()
147
	if err != nil {
148
		return err
149
	}
150
	_, err = r.db.Exec(ctx, query, args...)
151
	return err
152
}
153
154
func (r *UserRepo) SetPassword(ctx context.Context, userID uuid.UUID, password string) error {
155
	query, args, err := pgq.
156
		Update("users").
157
		Set("password", password).
158
		Where(pgq.Eq{"id": userID.String()}).
159
		SQL()
160
	if err != nil {
161
		return err
162
	}
163
164
	_, err = r.db.Exec(ctx, query, args...)
165
	return err
166
}
167
168
func (r *UserRepo) CheckIfUserExists(ctx context.Context, id uuid.UUID) (bool, error) {
169
	var exists bool
170
	err := r.db.QueryRow(
171
		ctx,
172
		`SELECT EXISTS(SELECT 1 FROM users WHERE id = $1)`,
173
		id.String(),
174
	).Scan(&exists)
175
	if errors.Is(err, pgx.ErrNoRows) {
176
		return false, models.ErrUserNotFound
177
	}
178
179
	return exists, err
180
}
181
182
func (r *UserRepo) CheckIfUserIsActivated(ctx context.Context, id uuid.UUID) (bool, error) {
183
	var activated bool
184
	err := r.db.QueryRow(ctx, `SELECT activated FROM users WHERE id = $1`, id.String()).
185
		Scan(&activated)
186
	if errors.Is(err, pgx.ErrNoRows) {
187
		return false, models.ErrUserNotFound
188
	}
189
	return activated, err
190
}