spark-server/internal/module/square/logic/comment.go

126 lines
3.5 KiB
Go

package logic
import (
"context"
"strings"
"time"
"unicode/utf8"
"spark-server/internal/module/square/entity"
"spark-server/internal/pkg/errs"
"spark-server/internal/pkg/i18n"
"spark-server/internal/pkg/reqctx"
"spark-server/internal/pkg/validate"
)
type CommentInput struct {
Text string `json:"text" validate:"required,min=1,max=500" label:"field.square_comment" errmsg:"required:validate.square.comment_required;min:validate.square.comment_invalid;max:validate.square.comment_invalid"`
ReplyToID int64 `json:"reply_to_id" validate:"omitempty,gt=0" label:"field.square_reply"`
}
func (s *Service) Comment(ctx context.Context, userID, postID int64, channel entity.Channel, in *CommentInput) (*View, error) {
post, err := s.visiblePost(ctx, userID, postID, channel)
if err != nil {
return nil, err
}
if in == nil {
return nil, errs.ErrInvalidArgument
}
if err := validate.StructLang(reqctx.Lang(ctx), in); err != nil {
return nil, err
}
if !s.allowInteract(ctx, userID) {
return nil, errs.ErrRateLimited
}
text := strings.TrimSpace(in.Text)
if text == "" || utf8.RuneCountInString(text) > entity.MaxCommentRunes {
return nil, errs.ErrInvalidArgument.WithMsg(i18n.T(reqctx.Lang(ctx), i18n.KeySquareCommentInvalid, nil))
}
text, err = s.gateText(ctx, userID, text)
if err != nil {
return nil, err
}
parent, err := s.loadParent(ctx, postID, in.ReplyToID)
if err != nil {
return nil, err
}
id, err := s.ids.NextID()
if err != nil {
return nil, err
}
now := time.Now().UnixMilli()
row := &entity.Comment{
ID: id, PostID: postID, UserID: userID, Text: text, Status: entity.StatusNormal, CreatedAtMS: now,
}
if parent != nil {
row.ReplyToID = parent.ID
row.ReplyToUID = parent.UserID
}
if err := s.repo.AddComment(ctx, row); err != nil {
return nil, err
}
_ = s.notify(ctx, commentNotices(post, row, userID))
return s.Get(ctx, userID, postID, channel)
}
func (s *Service) DeleteComment(ctx context.Context, userID, postID, commentID int64, channel entity.Channel) (*View, error) {
post, err := s.visiblePost(ctx, userID, postID, channel)
if err != nil {
return nil, err
}
c, err := s.repo.GetComment(ctx, commentID)
if err != nil {
return nil, err
}
if c.PostID != postID || c.Status == entity.StatusDeleted {
return nil, errs.ErrNotFound
}
if c.UserID != userID && post.UserID != userID {
return nil, errs.ErrForbidden
}
if err := s.repo.SoftDeleteComment(ctx, commentID); err != nil {
return nil, err
}
return s.Get(ctx, userID, postID, channel)
}
func (s *Service) loadParent(ctx context.Context, postID, replyToID int64) (*entity.Comment, error) {
if replyToID <= 0 {
return nil, nil
}
parent, err := s.repo.GetComment(ctx, replyToID)
if err != nil {
return nil, err
}
if parent.PostID != postID || parent.Status != entity.StatusNormal {
return nil, errs.ErrInvalidArgument.WithMsg(i18n.T(reqctx.Lang(ctx), i18n.KeySquareReplyInvalid, nil))
}
return parent, nil
}
func commentNotices(post *entity.Post, c *entity.Comment, from int64) []*entity.Notice {
seen := map[int64]struct{}{}
out := make([]*entity.Notice, 0, 2)
ch := post.Channel
if ch == "" {
ch = entity.ChannelSquare
}
add := func(to int64, kind entity.NoticeKind) {
if to <= 0 || to == from {
return
}
if _, ok := seen[to]; ok {
return
}
seen[to] = struct{}{}
out = append(out, &entity.Notice{
ToUserID: to, Channel: ch, FromUserID: from, Kind: kind, PostID: post.ID, CommentID: c.ID,
})
}
if c.ReplyToUID > 0 {
add(c.ReplyToUID, entity.NoticeReply)
}
add(post.UserID, entity.NoticeComment)
return out
}