feat(mutation): AddComment with path enumeration, bumps post
This commit is contained in:
parent
425f104bc0
commit
b3b0aba5e5
1 changed files with 116 additions and 0 deletions
116
mutations_add_comment.go
Normal file
116
mutations_add_comment.go
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// AddComment — appends a comment to a post. Bumps the post. Computes the
|
||||
// comment path from the parent comment's path. Pre-validated; the lock is
|
||||
// held during apply so the path computation + index update is atomic.
|
||||
type AddComment struct {
|
||||
PostID string
|
||||
ParentID string // empty for top-level
|
||||
AuthorID string
|
||||
Body string
|
||||
|
||||
// Set by Apply.
|
||||
resolvedCommentID string
|
||||
resolvedPath string
|
||||
resolvedDepth int
|
||||
}
|
||||
|
||||
func (m *AddComment) Kind() string { return "add_comment" }
|
||||
|
||||
func (m *AddComment) AffectedAuthors() []string {
|
||||
return []string{m.AuthorID}
|
||||
}
|
||||
|
||||
func (m *AddComment) Validate(s *Store) error {
|
||||
if strings.TrimSpace(m.AuthorID) == "" {
|
||||
return &ValidationError{Field: "author_id", Message: "required"}
|
||||
}
|
||||
if _, ok := s.GetAuthor(m.AuthorID); !ok {
|
||||
return &ValidationError{Field: "author_id", Message: fmt.Sprintf("unknown author %q", m.AuthorID)}
|
||||
}
|
||||
if strings.TrimSpace(m.PostID) == "" {
|
||||
return &ValidationError{Field: "post_id", Message: "required"}
|
||||
}
|
||||
post, ok := s.GetPost(m.PostID)
|
||||
if !ok {
|
||||
return &ValidationError{Field: "post_id", Message: fmt.Sprintf("unknown post %q", m.PostID)}
|
||||
}
|
||||
if post.IsDeleted {
|
||||
return &ValidationError{Field: "post_id", Message: "post is deleted"}
|
||||
}
|
||||
if m.ParentID != "" {
|
||||
comments := s.ListComments(m.PostID)
|
||||
found := false
|
||||
for _, c := range comments {
|
||||
if c.ID == m.ParentID && !c.IsDeleted {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return &ValidationError{Field: "parent_id", Message: fmt.Sprintf("unknown parent %q on post %q", m.ParentID, m.PostID)}
|
||||
}
|
||||
}
|
||||
if strings.TrimSpace(m.Body) == "" {
|
||||
return &ValidationError{Field: "body", Message: "required"}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *AddComment) Apply(s *Store, lock LockRegion) error {
|
||||
now := s.Now()
|
||||
id := s.NewID()
|
||||
|
||||
// Compute path under the lock. Look up parent's path if any.
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
var path string
|
||||
var depth int
|
||||
if m.ParentID != "" {
|
||||
parent, ok := s.comments[m.ParentID]
|
||||
if !ok {
|
||||
return fmt.Errorf("parent comment disappeared mid-apply: %s", m.ParentID)
|
||||
}
|
||||
path = parent.Path + "." + id
|
||||
depth = parent.Depth + 1
|
||||
} else {
|
||||
path = id
|
||||
depth = 0
|
||||
}
|
||||
|
||||
c := &Comment{
|
||||
ID: id,
|
||||
PostID: m.PostID,
|
||||
ParentID: m.ParentID,
|
||||
Path: path,
|
||||
Depth: depth,
|
||||
AuthorID: m.AuthorID,
|
||||
Body: m.Body,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
m.resolvedCommentID = id
|
||||
m.resolvedPath = path
|
||||
m.resolvedDepth = depth
|
||||
s.comments[id] = c
|
||||
s.indexInsertComment(c)
|
||||
|
||||
// Bump the post.
|
||||
post, ok := s.posts[m.PostID]
|
||||
if !ok {
|
||||
return fmt.Errorf("post disappeared mid-apply: %s", m.PostID)
|
||||
}
|
||||
post.BumpedAt = now
|
||||
post.UpdatedAt = now
|
||||
s.indexUpdatePost(post)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *AddComment) ResolvedCommentID() string { return m.resolvedCommentID }
|
||||
func (m *AddComment) ResolvedPath() string { return m.resolvedPath }
|
||||
func (m *AddComment) ResolvedDepth() int { return m.resolvedDepth }
|
||||
Loading…
Add table
Add a link
Reference in a new issue