all repos

onasty @ c2e1526

a one-time notes service

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
package vertokrepo

import (
	"context"
	"time"

	"github.com/gofrs/uuid/v5"
	"github.com/henvic/pgq"
	"github.com/olexsmir/onasty/internal/models"
	"github.com/olexsmir/onasty/internal/store/psqlutil"
)

type VerificationTokenStorer interface {
	Create(
		ctx context.Context,
		token string,
		userID uuid.UUID,
		createdAt, expiresAt time.Time,
	) error

	GetUserIDByTokenAndMarkAsUsed(
		ctx context.Context,
		token string,
		usedAT time.Time,
	) (uuid.UUID, error)

	GetTokenOrUpdateTokenByUserID(
		ctx context.Context,
		userID uuid.UUID,
		token string,
		tokenExpirationTime time.Time,
	) (string, error)
}

var _ VerificationTokenStorer = (*VerificationTokenRepo)(nil)

type VerificationTokenRepo struct {
	db *psqlutil.DB
}

func New(db *psqlutil.DB) *VerificationTokenRepo {
	return &VerificationTokenRepo{
		db: db,
	}
}

func (r *VerificationTokenRepo) Create(
	ctx context.Context,
	token string,
	userID uuid.UUID,
	createdAt, expiresAt time.Time,
) error {
	query, aggs, err := pgq.
		Insert("verification_tokens").
		Columns("user_id", "token", "created_at", "expires_at").
		Values(userID, token, createdAt, expiresAt).
		SQL()
	if err != nil {
		return err
	}

	_, err = r.db.Exec(ctx, query, aggs...)
	return err
}

func (r *VerificationTokenRepo) GetUserIDByTokenAndMarkAsUsed(
	ctx context.Context,
	token string,
	usedAt time.Time,
) (uuid.UUID, error) {
	tx, err := r.db.Begin(ctx)
	if err != nil {
		return uuid.Nil, err
	}
	defer tx.Rollback(ctx) //nolint:errcheck

	var isUsed bool
	err = tx.QueryRow(ctx, "select used_at is not null from verification_tokens where token = $1", token).
		Scan(&isUsed)
	if err != nil {
		return uuid.Nil, err
	}

	if isUsed {
		return uuid.Nil, models.ErrUserIsAlreeadyVerified
	}

	query := `--sql
update verification_tokens
set used_at = $1
where token = $2
returning user_id`

	var userID uuid.UUID
	err = tx.QueryRow(ctx, query, usedAt, token).Scan(&userID)
	if err != nil {
		return uuid.Nil, err
	}

	return userID, tx.Commit(ctx)
}

func (r *VerificationTokenRepo) GetTokenOrUpdateTokenByUserID(
	ctx context.Context,
	userID uuid.UUID,
	token string,
	tokenExpirationTime time.Time,
) (string, error) {
	query := `--sql
insert into verification_tokens (user_id, token, expires_at)
values ($1, $2, $3)
on conflict (user_id)
  do update set
    token = $2,
    expires_at = $3
returning token`

	var res string
	err := r.db.QueryRow(ctx, query, userID, token, tokenExpirationTime).Scan(&res)
	return res, err
}