onasty/internal/store/psql/noterepo/noterepo.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 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
package noterepo
import (
"context"
"errors"
"time"
"github.com/gofrs/uuid/v5"
"github.com/henvic/pgq"
"github.com/jackc/pgx/v5"
"github.com/olexsmir/onasty/internal/dtos"
"github.com/olexsmir/onasty/internal/models"
"github.com/olexsmir/onasty/internal/store/psqlutil"
)
type NoteStorer interface {
// Create creates a note.
Create(ctx context.Context, inp dtos.CreateNoteDTO) error
// GetBySlug gets a note by slug.
// Returns [models.ErrNoteNotFound] if note is not found.
GetBySlug(ctx context.Context, slug dtos.NoteSlugDTO) (dtos.NoteDTO, error)
// GetBySlugAndPassword gets a note by slug and password.
// the "password" should be hashed.
//
// Returns [models.ErrNoteNotFound] if note is not found.
GetBySlugAndPassword(
ctx context.Context,
slug dtos.NoteSlugDTO,
password string,
) (dtos.NoteDTO, error)
// RemoveBySlug marks note as read, deletes it's content, and keeps meta data
// Returns [models.ErrNoteNotFound] if note is not found.
RemoveBySlug(ctx context.Context, slug dtos.NoteSlugDTO, readAt time.Time) error
// SetAuthorIDBySlug assigns author to note by slug.
// Returns [models.ErrNoteNotFound] if note is not found.
SetAuthorIDBySlug(ctx context.Context, slug dtos.NoteSlugDTO, authorID uuid.UUID) error
}
var _ NoteStorer = (*NoteRepo)(nil)
type NoteRepo struct {
db *psqlutil.DB
}
func New(db *psqlutil.DB) *NoteRepo {
return &NoteRepo{db}
}
func (s *NoteRepo) Create(ctx context.Context, inp dtos.CreateNoteDTO) error {
query, args, err := pgq.
Insert("notes").
Columns("content", "slug", "password", "burn_before_expiration ", "created_at", "expires_at").
Values(inp.Content, inp.Slug, inp.Password, inp.BurnBeforeExpiration, inp.CreatedAt, inp.ExpiresAt).
SQL()
if err != nil {
return err
}
_, err = s.db.Exec(ctx, query, args...)
if psqlutil.IsDuplicateErr(err) {
return models.ErrNoteSlugIsAlreadyInUse
}
return err
}
func (s *NoteRepo) GetBySlug(ctx context.Context, slug dtos.NoteSlugDTO) (dtos.NoteDTO, error) {
query, args, err := pgq.
Select("content", "slug", "burn_before_expiration", "read_at", "created_at", "expires_at").
From("notes").
Where("(password is null or password = '')").
Where(pgq.Eq{"slug": slug}).
SQL()
if err != nil {
return dtos.NoteDTO{}, err
}
var note dtos.NoteDTO
err = s.db.QueryRow(ctx, query, args...).
Scan(¬e.Content, ¬e.Slug, ¬e.BurnBeforeExpiration, ¬e.ReadAt, ¬e.CreatedAt, ¬e.ExpiresAt)
if errors.Is(err, pgx.ErrNoRows) {
return dtos.NoteDTO{}, models.ErrNoteNotFound
}
return note, err
}
func (s *NoteRepo) GetBySlugAndPassword(
ctx context.Context,
slug dtos.NoteSlugDTO,
passwd string,
) (dtos.NoteDTO, error) {
query, args, err := pgq.
Select("content", "slug", "burn_before_expiration", "read_at", "created_at", "expires_at").
From("notes").
Where(pgq.Eq{
"slug": slug,
"password": passwd,
}).
SQL()
if err != nil {
return dtos.NoteDTO{}, err
}
var note dtos.NoteDTO
err = s.db.QueryRow(ctx, query, args...).
Scan(¬e.Content, ¬e.Slug, ¬e.BurnBeforeExpiration, ¬e.ReadAt, ¬e.CreatedAt, ¬e.ExpiresAt)
if errors.Is(err, pgx.ErrNoRows) {
return dtos.NoteDTO{}, models.ErrNoteNotFound
}
return note, err
}
func (s *NoteRepo) RemoveBySlug(
ctx context.Context,
slug dtos.NoteSlugDTO,
readAt time.Time,
) error {
query, args, err := pgq.
Update("notes").
Set("content", "").
Set("read_at", readAt).
Where(pgq.Eq{
"slug": slug,
"read_at": nil,
}).
SQL()
if err != nil {
return err
}
_, err = s.db.Exec(ctx, query, args...)
if errors.Is(err, pgx.ErrNoRows) {
return models.ErrNoteNotFound
}
return err
}
func (s *NoteRepo) SetAuthorIDBySlug(
ctx context.Context,
slug dtos.NoteSlugDTO,
authorID uuid.UUID,
) error {
tx, err := s.db.Begin(ctx)
if err != nil {
return err
}
defer tx.Rollback(ctx) //nolint:errcheck
var noteID uuid.UUID
err = tx.QueryRow(ctx, "select id from notes where slug = $1", slug).Scan(¬eID)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return models.ErrNoteNotFound
}
return err
}
_, err = tx.Exec(
ctx,
"insert into notes_authors (note_id, user_id) values ($1, $2)",
noteID, authorID,
)
if err != nil {
return err
}
return tx.Commit(ctx)
}
|