all repos

onasty @ 51d3b53b0c8469cfd0ca12bb2a505d7fe77d228e

a one-time notes service

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

Smirnov Oleksandr Smirnov Oleksandr
ss2316544@gmail.com
fix: oauth state (#128)..., 12 months 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/models"
11
	"github.com/olexsmir/onasty/internal/store/psqlutil"
12
)
13
14
type UserStorer interface {
15
	Create(ctx context.Context, inp models.User) (uuid.UUID, error)
16
17
	// GetByEmail returns user by email and password
18
	// the password should be hashed
19
	GetByEmail(ctx context.Context, email string) (models.User, error)
20
	GetUserIDByEmail(ctx context.Context, email string) (uuid.UUID, error)
21
	GetByID(ctx context.Context, userID uuid.UUID) (models.User, error)
22
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, 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
	GetByOAuthID(ctx context.Context, provider, providerID string) (models.User, error)
34
	LinkOAuthIdentity(ctx context.Context, userID uuid.UUID, provider, providerID string) error
35
36
	CheckIfUserExists(ctx context.Context, userID uuid.UUID) (bool, error)
37
	CheckIfUserIsActivated(ctx context.Context, userID uuid.UUID) (bool, error)
38
}
39
40
var _ UserStorer = (*UserRepo)(nil)
41
42
type UserRepo struct {
43
	db *psqlutil.DB
44
}
45
46
func New(db *psqlutil.DB) *UserRepo {
47
	return &UserRepo{
48
		db: db,
49
	}
50
}
51
52
func (r *UserRepo) Create(ctx context.Context, inp models.User) (uuid.UUID, error) {
53
	query, args, err := pgq.
54
		Insert("users").
55
		Columns("email", "password", "activated", "created_at", "last_login_at").
56
		Values(inp.Email, inp.Password, inp.Activated, inp.CreatedAt, inp.LastLoginAt).
57
		Returning("id").
58
		SQL()
59
	if err != nil {
60
		return uuid.UUID{}, err
61
	}
62
63
	var id uuid.UUID
64
	err = r.db.QueryRow(ctx, query, args...).Scan(&id)
65
66
	// FIXME: somehow this does return errors but i can't errors.Is them in api layer
67
	if psqlutil.IsDuplicateErr(err, "users_email_key") {
68
		return uuid.UUID{}, models.ErrUserEmailIsAlreadyInUse
69
	}
70
71
	return id, err
72
}
73
74
func (r *UserRepo) GetByEmail(
75
	ctx context.Context,
76
	email string,
77
) (models.User, error) {
78
	query, args, err := pgq.
79
		Select("id", "email", "password", "activated", "created_at", "last_login_at").
80
		From("users").
81
		Where(pgq.Eq{"email": email}).
82
		SQL()
83
	if err != nil {
84
		return models.User{}, err
85
	}
86
87
	var user models.User
88
	err = r.db.QueryRow(ctx, query, args...).
89
		Scan(&user.ID, &user.Email, &user.Password, &user.Activated, &user.CreatedAt, &user.LastLoginAt)
90
	if errors.Is(err, pgx.ErrNoRows) {
91
		return models.User{}, models.ErrUserNotFound
92
	}
93
94
	return user, err
95
}
96
97
func (r *UserRepo) GetUserIDByEmail(ctx context.Context, email string) (uuid.UUID, error) {
98
	query, args, err := pgq.
99
		Select("id").
100
		From("users").
101
		Where(pgq.Eq{"email": email}).
102
		SQL()
103
	if err != nil {
104
		return uuid.Nil, err
105
	}
106
107
	var id uuid.UUID
108
	err = r.db.QueryRow(ctx, query, args...).Scan(&id)
109
	if errors.Is(err, pgx.ErrNoRows) {
110
		return uuid.Nil, models.ErrUserNotFound
111
	}
112
113
	return id, err
114
}
115
116
func (r *UserRepo) GetByID(ctx context.Context, userID uuid.UUID) (models.User, error) {
117
	query := `--sql
118
select id, email, password, activated, created_at, last_login_at
119
from users
120
where id = $1`
121
122
	var user models.User
123
	err := r.db.QueryRow(ctx, query, userID).
124
		Scan(&user.ID, &user.Email, &user.Password, &user.Activated, &user.CreatedAt, &user.LastLoginAt)
125
	if errors.Is(err, pgx.ErrNoRows) {
126
		return models.User{}, models.ErrUserNotFound
127
	}
128
129
	return user, err
130
}
131
132
func (r *UserRepo) GetByOAuthID(
133
	ctx context.Context,
134
	provider, providerID string,
135
) (models.User, error) {
136
	query := `--sql
137
	select u.id, u.email, u.password, u.activated, u.created_at, u.last_login_at
138
	from users u
139
	join oauth_identities oi on u.id = oi.user_id
140
	where oi.provider = $1
141
		and oi.provider_id = $2
142
	limit 1`
143
144
	var user models.User
145
	err := r.db.QueryRow(ctx, query, provider, providerID).
146
		Scan(&user.ID, &user.Email, &user.Password, &user.Activated, &user.CreatedAt, &user.LastLoginAt)
147
	if errors.Is(err, pgx.ErrNoRows) {
148
		return models.User{}, models.ErrUserNotFound
149
	}
150
151
	return user, err
152
}
153
154
func (r *UserRepo) LinkOAuthIdentity(
155
	ctx context.Context,
156
	userID uuid.UUID,
157
	provider, providerID string,
158
) error {
159
	query := `--sql
160
insert into oauth_identities (user_id, provider, provider_id)
161
values ($1, $2, $3)
162
on conflict (provider, provider_id) do update
163
set user_id = $1,
164
	provider = $2,
165
	provider_id = $3`
166
167
	_, err := r.db.Exec(ctx, query, userID, provider, providerID)
168
	return err
169
}
170
171
func (r *UserRepo) MarkUserAsActivated(ctx context.Context, id uuid.UUID) error {
172
	query, args, err := pgq.
173
		Update("users").
174
		Set("activated ", true).
175
		Where(pgq.Eq{"id": id.String()}).
176
		SQL()
177
	if err != nil {
178
		return err
179
	}
180
181
	_, err = r.db.Exec(ctx, query, args...)
182
	return err
183
}
184
185
func (r *UserRepo) ChangePassword(
186
	ctx context.Context,
187
	userID uuid.UUID,
188
	newPasswd string,
189
) error {
190
	query, args, err := pgq.
191
		Update("users").
192
		Set("password", newPasswd).
193
		Where(pgq.Eq{"id": userID.String()}).
194
		SQL()
195
	if err != nil {
196
		return err
197
	}
198
	_, err = r.db.Exec(ctx, query, args...)
199
	return err
200
}
201
202
func (r *UserRepo) SetPassword(ctx context.Context, userID uuid.UUID, password string) error {
203
	query, args, err := pgq.
204
		Update("users").
205
		Set("password", password).
206
		Where(pgq.Eq{"id": userID.String()}).
207
		SQL()
208
	if err != nil {
209
		return err
210
	}
211
212
	_, err = r.db.Exec(ctx, query, args...)
213
	return err
214
}
215
216
func (r *UserRepo) CheckIfUserExists(ctx context.Context, id uuid.UUID) (bool, error) {
217
	var exists bool
218
	err := r.db.QueryRow(
219
		ctx,
220
		`SELECT EXISTS(SELECT 1 FROM users WHERE id = $1)`,
221
		id.String(),
222
	).Scan(&exists)
223
	if errors.Is(err, pgx.ErrNoRows) {
224
		return false, models.ErrUserNotFound
225
	}
226
227
	return exists, err
228
}
229
230
func (r *UserRepo) CheckIfUserIsActivated(ctx context.Context, id uuid.UUID) (bool, error) {
231
	var activated bool
232
	err := r.db.QueryRow(ctx, `SELECT activated FROM users WHERE id = $1`, id.String()).
233
		Scan(&activated)
234
	if errors.Is(err, pgx.ErrNoRows) {
235
		return false, models.ErrUserNotFound
236
	}
237
	return activated, err
238
}