122 lines
3.4 KiB
Go
122 lines
3.4 KiB
Go
package logic
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"time"
|
|
"unicode/utf8"
|
|
|
|
"spark-server/internal/module/moment/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.moment_comment" errmsg:"required:validate.moment.comment_required;min:validate.moment.comment_invalid;max:validate.moment.comment_invalid"`
|
|
ReplyToID int64 `json:"reply_to_id" validate:"omitempty,gt=0" label:"field.moment_reply"`
|
|
}
|
|
|
|
func (s *Service) Comment(ctx context.Context, userID, postID int64, in *CommentInput) (*View, error) {
|
|
post, err := s.visiblePost(ctx, userID, postID)
|
|
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.KeyMomentCommentInvalid, 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)
|
|
}
|
|
|
|
func (s *Service) DeleteComment(ctx context.Context, userID, postID, commentID int64) (*View, error) {
|
|
post, err := s.visiblePost(ctx, userID, postID)
|
|
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)
|
|
}
|
|
|
|
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.KeyMomentReplyInvalid, 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)
|
|
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, 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
|
|
}
|