all repos

onasty @ eb4c605

a one-time notes service

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

Olexandr Smirnov Olexandr Smirnov
olexsmir@gmail.com
fix(changeemailrepo): use correct version of pgx (#193), 9 months ago
1
package changeemailrepo
2
3
import (
4
	"context"
5
	"errors"
6
	"time"
7
8
	"github.com/jackc/pgx/v5"
9
	"github.com/olexsmir/onasty/internal/models"
10
	"github.com/olexsmir/onasty/internal/store/psqlutil"
11
)
12
13
type ChangeEmailStorer interface {
14
	// Create create a change email token.
15
	Create(ctx context.Context, input models.ChangeEmailToken) error
16
17
	// GetByToken returns change email token by its token.
18
	// Returns [models.ErrChangeEmailTokenNotFound] if not found.
19
	GetByToken(ctx context.Context, token string) (models.ChangeEmailToken, error)
20
21
	// MarkAsUsed marks change email token as used.
22
	// If not found, returns [models.ErrChangeEmailTokenNotFound].
23
	// If token is already used, returns [models.ErrChangeEmailTokenIsAlreadyUsed].
24
	// If token is expired, returns [models.ErrChangeEmailTokenExpired]
25
	MarkAsUsed(ctx context.Context, token string, usedAT time.Time) error
26
}
27
28
var _ ChangeEmailStorer = (*ChangeEmailRepo)(nil)
29
30
type ChangeEmailRepo struct {
31
	db *psqlutil.DB
32
}
33
34
func New(db *psqlutil.DB) *ChangeEmailRepo {
35
	return &ChangeEmailRepo{
36
		db: db,
37
	}
38
}
39
40
func (c *ChangeEmailRepo) Create(ctx context.Context, inp models.ChangeEmailToken) error {
41
	query := `--sql
42
insert into change_email_tokens (user_id, new_email, token, created_at, expires_at)
43
values ($1, $2, $3, $4, $5)
44
`
45
46
	_, err := c.db.Exec(ctx, query,
47
		inp.UserID, inp.NewEmail, inp.Token, inp.CreatedAt, inp.ExpiresAt)
48
	return err
49
}
50
51
func (c *ChangeEmailRepo) GetByToken(
52
	ctx context.Context,
53
	token string,
54
) (models.ChangeEmailToken, error) {
55
	query := `--sql
56
select user_id, new_email, token, created_at, expires_at
57
from change_email_tokens
58
where token = $1
59
`
60
61
	var res models.ChangeEmailToken
62
	err := c.db.QueryRow(ctx, query, token).
63
		Scan(&res.UserID, &res.NewEmail, &res.Token, &res.CreatedAt, &res.ExpiresAt)
64
	if errors.Is(err, pgx.ErrNoRows) {
65
		return models.ChangeEmailToken{}, models.ErrChangeEmailTokenNotFound
66
	}
67
68
	return res, err
69
}
70
71
func (c *ChangeEmailRepo) MarkAsUsed(ctx context.Context, token string, usedAT time.Time) error {
72
	tx, err := c.db.Begin(ctx)
73
	if err != nil {
74
		return err
75
	}
76
	defer tx.Rollback(ctx) //nolint:errcheck
77
78
	var isUsed bool
79
	var expiresAt time.Time
80
	err = tx.QueryRow(ctx,
81
		"select (used_at is not null), expires_at from change_email_tokens where token = $1",
82
		token).
83
		Scan(&isUsed, &expiresAt)
84
	if err != nil {
85
		if errors.Is(err, pgx.ErrNoRows) {
86
			return models.ErrChangeEmailTokenNotFound
87
		}
88
		return err
89
	}
90
91
	if isUsed {
92
		return models.ErrChangeEmailTokenIsAlreadyUsed
93
	}
94
95
	if time.Now().After(expiresAt) {
96
		return models.ErrChangeEmailTokenExpired
97
	}
98
99
	query := `--sql
100
update change_email_tokens
101
set used_at = $1
102
where token = $2`
103
104
	_, err = tx.Exec(ctx, query, usedAT, token)
105
	if err != nil {
106
		return err
107
	}
108
109
	return tx.Commit(ctx)
110
}