all repos

onasty @ 445822dad429b08586716d6fea39fa538f056c51

a one-time notes service

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

Smirnov Oleksandr Smirnov Oleksandr
ss2316544@gmail.com
feat: add password support to notes (#41)..., 1 year ago
1
package noterepo
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 NoteStorer interface {
16
	// Create creates a note.
17
	Create(ctx context.Context, inp dtos.CreateNoteDTO) error
18
19
	// GetBySlug gets a note by slug.
20
	// Returns [models.ErrNoteNotFound] if note is not found.
21
	GetBySlug(ctx context.Context, slug dtos.NoteSlugDTO) (dtos.NoteDTO, error)
22
23
	// GetBySlugAndPassword gets a note by slug and password.
24
	// the "password" should be hashed.
25
	//
26
	// Returns [models.ErrNoteNotFound] if note is not found.
27
	GetBySlugAndPassword(
28
		ctx context.Context,
29
		slug dtos.NoteSlugDTO,
30
		password string,
31
	) (dtos.NoteDTO, error)
32
33
	// DeleteBySlug deletes note by slug or returns [models.ErrNoteNotFound] if note if not found.
34
	DeleteBySlug(ctx context.Context, slug dtos.NoteSlugDTO) error
35
36
	// SetAuthorIDBySlug assigns author to note by slug.
37
	// Returns [models.ErrNoteNotFound] if note is not found.
38
	SetAuthorIDBySlug(ctx context.Context, slug dtos.NoteSlugDTO, authorID uuid.UUID) error
39
}
40
41
var _ NoteStorer = (*NoteRepo)(nil)
42
43
type NoteRepo struct {
44
	db *psqlutil.DB
45
}
46
47
func New(db *psqlutil.DB) *NoteRepo {
48
	return &NoteRepo{db}
49
}
50
51
func (s *NoteRepo) Create(ctx context.Context, inp dtos.CreateNoteDTO) error {
52
	query, args, err := pgq.
53
		Insert("notes").
54
		Columns("content", "slug", "password", "burn_before_expiration ", "created_at", "expires_at").
55
		Values(inp.Content, inp.Slug, inp.Password, inp.BurnBeforeExpiration, inp.CreatedAt, inp.ExpiresAt).
56
		SQL()
57
	if err != nil {
58
		return err
59
	}
60
61
	_, err = s.db.Exec(ctx, query, args...)
62
	if psqlutil.IsDuplicateErr(err) {
63
		return models.ErrNoteSlugIsAlreadyInUse
64
	}
65
66
	return err
67
}
68
69
func (s *NoteRepo) GetBySlug(ctx context.Context, slug dtos.NoteSlugDTO) (dtos.NoteDTO, error) {
70
	query, args, err := pgq.
71
		Select("content", "slug", "burn_before_expiration", "created_at", "expires_at").
72
		From("notes").
73
		Where("(password is null or password = '')").
74
		Where(pgq.Eq{"slug": slug}).
75
		SQL()
76
	if err != nil {
77
		return dtos.NoteDTO{}, err
78
	}
79
80
	var note dtos.NoteDTO
81
	err = s.db.QueryRow(ctx, query, args...).
82
		Scan(&note.Content, &note.Slug, &note.BurnBeforeExpiration, &note.CreatedAt, &note.ExpiresAt)
83
84
	if errors.Is(err, pgx.ErrNoRows) {
85
		return dtos.NoteDTO{}, models.ErrNoteNotFound
86
	}
87
88
	return note, err
89
}
90
91
func (s *NoteRepo) GetBySlugAndPassword(
92
	ctx context.Context,
93
	slug dtos.NoteSlugDTO,
94
	passwd string,
95
) (dtos.NoteDTO, error) {
96
	query, args, err := pgq.
97
		Select("content", "slug", "burn_before_expiration", "created_at", "expires_at").
98
		From("notes").
99
		Where(pgq.Eq{
100
			"slug":     slug,
101
			"password": passwd,
102
		}).
103
		SQL()
104
	if err != nil {
105
		return dtos.NoteDTO{}, err
106
	}
107
108
	var note dtos.NoteDTO
109
	err = s.db.QueryRow(ctx, query, args...).
110
		Scan(&note.Content, &note.Slug, &note.BurnBeforeExpiration, &note.CreatedAt, &note.ExpiresAt)
111
112
	if errors.Is(err, pgx.ErrNoRows) {
113
		return dtos.NoteDTO{}, models.ErrNoteNotFound
114
	}
115
116
	return note, err
117
}
118
119
func (s *NoteRepo) DeleteBySlug(ctx context.Context, slug dtos.NoteSlugDTO) error {
120
	query, args, err := pgq.
121
		Delete("notes").
122
		Where(pgq.Eq{"slug": slug}).
123
		SQL()
124
	if err != nil {
125
		return err
126
	}
127
128
	_, err = s.db.Exec(ctx, query, args...)
129
	if errors.Is(err, pgx.ErrNoRows) {
130
		return models.ErrNoteNotFound
131
	}
132
133
	return err
134
}
135
136
func (s *NoteRepo) SetAuthorIDBySlug(
137
	ctx context.Context,
138
	slug dtos.NoteSlugDTO,
139
	authorID uuid.UUID,
140
) error {
141
	tx, err := s.db.Begin(ctx)
142
	if err != nil {
143
		return err
144
	}
145
	defer tx.Rollback(ctx) //nolint:errcheck
146
147
	var noteID uuid.UUID
148
	err = tx.QueryRow(ctx, "select id from notes where slug = $1", slug).Scan(&noteID)
149
	if err != nil {
150
		if errors.Is(err, pgx.ErrNoRows) {
151
			return models.ErrNoteNotFound
152
		}
153
		return err
154
	}
155
156
	_, err = tx.Exec(
157
		ctx,
158
		"insert into notes_authors (note_id, user_id) values ($1, $2)",
159
		noteID, authorID,
160
	)
161
	if err != nil {
162
		return err
163
	}
164
165
	return tx.Commit(ctx)
166
}