Skip to content

Commit

Permalink
feat(notes): create
Browse files Browse the repository at this point in the history
  • Loading branch information
olexsmir committed Jul 17, 2024
1 parent efada80 commit 45efe20
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 11 deletions.
23 changes: 23 additions & 0 deletions internal/dtos/note.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package dtos

import "time"

type NoteSlugDTO string

func (n NoteSlugDTO) String() string { return string(n) }

type NoteDTO struct {
Content string
Slug string
BurnBeforeExpiration bool
CreatedAt time.Time
ExpiresAt time.Time
}

type CreateNoteDTO struct {
Content string
Slug string
BurnBeforeExpiration bool
CreatedAt time.Time
ExpiresAt time.Time
}
5 changes: 3 additions & 2 deletions internal/models/notes.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import (
)

var (
ErrNoteContentIsEmpty = errors.New("note: content is empty")
ErrNoteExpired = errors.New("note: expired")
ErrNoteContentIsEmpty = errors.New("note: content is empty")
ErrNoteSlugIsAlreadyInUse = errors.New("note: slug is already in use")
ErrNoteExpired = errors.New("note: expired")
)

type Note struct {
Expand Down
26 changes: 24 additions & 2 deletions internal/service/notesrv/notesrv.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
package notesrv

import "github.com/olexsmir/onasty/internal/store/psql/noterepo"
import (
"context"

type NoteServicer interface{}
"github.com/google/uuid"
"github.com/olexsmir/onasty/internal/dtos"
"github.com/olexsmir/onasty/internal/store/psql/noterepo"
)

type NoteServicer interface {
Create(ctx context.Context, note dtos.CreateNoteDTO) (dtos.NoteSlugDTO, error)
GetBySlug(ctx context.Context, slug dtos.NoteSlugDTO) (dtos.NoteDTO, error)
}

var _ NoteServicer = (*NoteSrv)(nil)

Expand All @@ -15,3 +24,16 @@ func New(noterepo noterepo.NoteStorer) NoteServicer {
noterepo: noterepo,
}
}

func (n *NoteSrv) Create(ctx context.Context, inp dtos.CreateNoteDTO) (dtos.NoteSlugDTO, error) {
if inp.Slug == "" {
inp.Slug = uuid.New().String()
}

err := n.noterepo.Create(ctx, inp)
return dtos.NoteSlugDTO(inp.Slug), err
}

func (n *NoteSrv) GetBySlug(ctx context.Context, slug dtos.NoteSlugDTO) (dtos.NoteDTO, error) {
return dtos.NoteDTO{}, nil
}
37 changes: 33 additions & 4 deletions internal/store/psql/noterepo/noterepo.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,42 @@
package noterepo

import "github.com/olexsmir/onasty/internal/store/psqlutil"
import (
"context"

type NoteStorer interface{}
"github.com/henvic/pgq"
"github.com/olexsmir/onasty/internal/dtos"
"github.com/olexsmir/onasty/internal/models"
"github.com/olexsmir/onasty/internal/store/psqlutil"
)

type NoteStorer interface {
Create(ctx context.Context, inp dtos.CreateNoteDTO) error
}

var _ NoteStorer = (*NoteRepo)(nil)

type NoteRepo struct{}
type NoteRepo struct {
db *psqlutil.DB
}

func New(db *psqlutil.DB) NoteStorer {
return &NoteRepo{}
return &NoteRepo{db}
}

func (s *NoteRepo) Create(ctx context.Context, inp dtos.CreateNoteDTO) error {
query, args, err := pgq.
Insert("notes").
Columns("content", "slug", "burn_before_expiration ", "created_at", "expires_at").
Values(inp.Content, inp.Slug, 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
}
55 changes: 53 additions & 2 deletions internal/transport/http/apiv1/note.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,59 @@
package apiv1

import "github.com/gin-gonic/gin"
import (
"net/http"
"time"

func (a *APIV1) createNoteHandler(c *gin.Context) {}
"github.com/gin-gonic/gin"
"github.com/olexsmir/onasty/internal/dtos"
"github.com/olexsmir/onasty/internal/models"
)

type createNoteRequest struct {
Content string `json:"content"`
Slug string `json:"slug"`
BurnBeforeExpiration bool `json:"burn_before_expiration"`
ExpiresAt time.Time `json:"expires_at"`
}

type createNoteResponse struct {
Slug string `json:"slug"`
}

func (a *APIV1) createNoteHandler(c *gin.Context) {
var req createNoteRequest
if err := c.ShouldBindJSON(&req); err != nil {
newError(c, http.StatusBadRequest, "invalid request")
return
}

note := models.Note{
Content: req.Content,
Slug: req.Slug,
BurnBeforeExpiration: req.BurnBeforeExpiration,
CreatedAt: time.Now(),
ExpiresAt: req.ExpiresAt,
}

if err := note.Validate(); err != nil {
newErrorStatus(c, http.StatusBadRequest, err.Error())
return
}

slug, err := a.notesrv.Create(c.Request.Context(), dtos.CreateNoteDTO{
Content: note.Content,
Slug: note.Slug,
BurnBeforeExpiration: note.BurnBeforeExpiration,
CreatedAt: note.CreatedAt,
ExpiresAt: note.ExpiresAt,
})
if err != nil {
errorResponse(c, err)
return
}

c.JSON(http.StatusCreated, createNoteResponse{slug.String()})
}

func (a *APIV1) getNoteBySlugHandler(c *gin.Context) {
_ = c.Param("slug")
Expand Down
9 changes: 8 additions & 1 deletion internal/transport/http/apiv1/response.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,18 @@ type response struct {

func errorResponse(c *gin.Context, err error) {
if errors.Is(err, models.ErrUserEmailIsAlreadyInUse) ||
errors.Is(err, models.ErrUsernameIsAlreadyInUse) {
errors.Is(err, models.ErrUsernameIsAlreadyInUse) ||
errors.Is(err, models.ErrNoteContentIsEmpty) ||
errors.Is(err, models.ErrNoteSlugIsAlreadyInUse) {
newError(c, http.StatusBadRequest, err.Error())
return
}

if errors.Is(err, models.ErrNoteExpired) {
newError(c, http.StatusGone, err.Error())
return
}

if errors.Is(err, models.ErrUserNotFound) {
newErrorStatus(c, http.StatusBadRequest, err.Error())
return
Expand Down

0 comments on commit 45efe20

Please sign in to comment.