onasty/internal/service/notesrv/notesrv.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 |
package notesrv
import (
"context"
"log/slog"
"time"
"github.com/gofrs/uuid/v5"
"github.com/olexsmir/onasty/internal/dtos"
"github.com/olexsmir/onasty/internal/hasher"
"github.com/olexsmir/onasty/internal/models"
"github.com/olexsmir/onasty/internal/store/psql/noterepo"
"github.com/olexsmir/onasty/internal/store/rdb/notecache"
)
type NoteServicer interface {
// Create creates note
// if slug is empty it will be generated, otherwise used as is
// if userID is empty it means user isn't authorized so it will be used
Create(ctx context.Context, note dtos.CreateNoteDTO, userID uuid.UUID) (dtos.NoteSlugDTO, error)
// GetBySlugAndRemoveIfNeeded returns note by slug, and removes if if needed
GetBySlugAndRemoveIfNeeded(ctx context.Context, input GetNoteBySlugInput) (dtos.NoteDTO, error)
}
var _ NoteServicer = (*NoteSrv)(nil)
type NoteSrv struct {
noterepo noterepo.NoteStorer
hasher hasher.Hasher
cache notecache.NoteCacher
}
func New(noterepo noterepo.NoteStorer, hasher hasher.Hasher, cache notecache.NoteCacher) *NoteSrv {
return &NoteSrv{
noterepo: noterepo,
hasher: hasher,
cache: cache,
}
}
func (n *NoteSrv) Create(
ctx context.Context,
inp dtos.CreateNoteDTO,
userID uuid.UUID,
) (dtos.NoteSlugDTO, error) {
slog.DebugContext(ctx, "creating", "inp", inp)
if inp.Slug == "" {
inp.Slug = uuid.Must(uuid.NewV4()).String()
}
if inp.Password != "" {
hashedPassword, err := n.hasher.Hash(inp.Password)
if err != nil {
return "", err
}
inp.Password = hashedPassword
}
if err := n.noterepo.Create(ctx, inp); err != nil {
return "", err
}
if !userID.IsNil() {
if err := n.noterepo.SetAuthorIDBySlug(ctx, inp.Slug, userID); err != nil {
return "", err
}
}
return inp.Slug, nil
}
func (n *NoteSrv) GetBySlugAndRemoveIfNeeded(
ctx context.Context,
inp GetNoteBySlugInput,
) (dtos.NoteDTO, error) {
note, err := n.getNote(ctx, inp)
if err != nil {
return dtos.NoteDTO{}, err
}
m := models.Note{ //nolint:exhaustruct
ExpiresAt: note.ExpiresAt,
BurnBeforeExpiration: note.BurnBeforeExpiration,
}
if m.IsExpired() {
return dtos.NoteDTO{}, models.ErrNoteExpired
}
// since not every note should be burn before expiration
// we return early if it's not
if m.ShouldBeBurnt() {
return note, nil
}
return note, n.noterepo.RemoveBySlug(ctx, inp.Slug, time.Now())
}
func (n *NoteSrv) getNote(ctx context.Context, inp GetNoteBySlugInput) (dtos.NoteDTO, error) {
if r, err := n.cache.GetNote(ctx, inp.Slug); err == nil {
return r, nil
}
note, err := n.getNoteFromDBasedOnInput(ctx, inp)
if err != nil {
return dtos.NoteDTO{}, err
}
if note.ReadAt != nil && !note.ReadAt.IsZero() {
if err = n.cache.SetNote(ctx, inp.Slug, note); err != nil {
slog.ErrorContext(ctx, "notecache", "err", err)
}
}
return note, err
}
func (n *NoteSrv) getNoteFromDBasedOnInput(
ctx context.Context,
inp GetNoteBySlugInput,
) (dtos.NoteDTO, error) {
if inp.HasPassword() {
hashedPassword, err := n.hasher.Hash(inp.Password)
if err != nil {
return dtos.NoteDTO{}, err
}
return n.noterepo.GetBySlugAndPassword(ctx, inp.Slug, hashedPassword)
}
return n.noterepo.GetBySlug(ctx, inp.Slug)
}
|