初始提交:姜十三论坛 Jiang13 Forum
轻量自用论坛,Go 单二进制 + React SPA 内嵌 + SQLite。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
193
service/comment.go
Normal file
193
service/comment.go
Normal file
@@ -0,0 +1,193 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/mail"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/jiang13/forum/model"
|
||||
)
|
||||
|
||||
type CommentService struct {
|
||||
filter *SensitiveFilter
|
||||
}
|
||||
|
||||
func NewCommentService(filter *SensitiveFilter) *CommentService {
|
||||
return &CommentService{filter: filter}
|
||||
}
|
||||
|
||||
type CommentCreateInput struct {
|
||||
UserID uint
|
||||
PostID uint
|
||||
Content string
|
||||
ReplyTo *uint
|
||||
GuestNick string
|
||||
GuestEmail string
|
||||
GuestURL string
|
||||
IsPrivate bool
|
||||
}
|
||||
|
||||
func (s *CommentService) canViewPrivate(c model.Comment, viewerID uint, isAdmin bool, postAuthorID uint, guestSet map[uint]struct{}) bool {
|
||||
if !c.IsPrivate {
|
||||
return true
|
||||
}
|
||||
if isAdmin {
|
||||
return true
|
||||
}
|
||||
if viewerID > 0 && viewerID == postAuthorID {
|
||||
return true
|
||||
}
|
||||
if c.UserID > 0 && viewerID == c.UserID {
|
||||
return true
|
||||
}
|
||||
if _, ok := guestSet[c.ID]; ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *CommentService) fillReplyTargets(comments []model.Comment, loadMissing bool) {
|
||||
idMap := make(map[uint]model.Comment, len(comments))
|
||||
for _, c := range comments {
|
||||
idMap[c.ID] = c
|
||||
}
|
||||
for i := range comments {
|
||||
if comments[i].ReplyTo == nil {
|
||||
continue
|
||||
}
|
||||
if target, ok := idMap[*comments[i].ReplyTo]; ok {
|
||||
t := target
|
||||
comments[i].ReplyTarget = &t
|
||||
continue
|
||||
}
|
||||
if loadMissing {
|
||||
var target model.Comment
|
||||
if model.DB.Preload("User").First(&target, *comments[i].ReplyTo).Error == nil {
|
||||
comments[i].ReplyTarget = &target
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CommentService) ListByPost(postID, viewerID uint, isAdmin bool, postAuthorID uint, visibleGuestIDs []uint) ([]model.Comment, error) {
|
||||
var comments []model.Comment
|
||||
err := model.DB.Preload("User").Where("post_id = ?", postID).Order("floor asc").Find(&comments).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
guestSet := make(map[uint]struct{}, len(visibleGuestIDs))
|
||||
for _, id := range visibleGuestIDs {
|
||||
guestSet[id] = struct{}{}
|
||||
}
|
||||
|
||||
for i := range comments {
|
||||
if comments[i].IsPrivate && !s.canViewPrivate(comments[i], viewerID, isAdmin, postAuthorID, guestSet) {
|
||||
comments[i].ContentHidden = true
|
||||
comments[i].Content = ""
|
||||
}
|
||||
}
|
||||
|
||||
s.fillReplyTargets(comments, false)
|
||||
return comments, nil
|
||||
}
|
||||
|
||||
func (s *CommentService) Create(in CommentCreateInput) (*model.Comment, error) {
|
||||
content := s.filter.Filter(strings.TrimSpace(in.Content))
|
||||
if content == "" {
|
||||
return nil, errors.New("评论内容不能为空")
|
||||
}
|
||||
|
||||
var post model.Post
|
||||
if err := model.DB.First(&post, in.PostID).Error; err != nil {
|
||||
return nil, ErrPostNotFound
|
||||
}
|
||||
|
||||
if in.UserID > 0 {
|
||||
var user model.User
|
||||
if err := model.DB.First(&user, in.UserID).Error; err != nil {
|
||||
return nil, errors.New("用户不存在")
|
||||
}
|
||||
if user.Banned {
|
||||
return nil, errors.New("账号已被禁言")
|
||||
}
|
||||
} else {
|
||||
nick := strings.TrimSpace(in.GuestNick)
|
||||
if nick == "" {
|
||||
return nil, errors.New("请填写昵称")
|
||||
}
|
||||
if len([]rune(nick)) > 32 {
|
||||
return nil, errors.New("昵称过长")
|
||||
}
|
||||
if email := strings.TrimSpace(in.GuestEmail); email != "" {
|
||||
if _, err := mail.ParseAddress(email); err != nil {
|
||||
return nil, errors.New("邮箱格式不正确")
|
||||
}
|
||||
}
|
||||
if rawURL := strings.TrimSpace(in.GuestURL); rawURL != "" {
|
||||
u, err := url.ParseRequestURI(rawURL)
|
||||
if err != nil || u.Scheme == "" || u.Host == "" {
|
||||
return nil, errors.New("网址格式不正确")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var maxFloor int
|
||||
model.DB.Model(&model.Comment{}).Where("post_id = ?", in.PostID).Select("COALESCE(MAX(floor), 0)").Scan(&maxFloor)
|
||||
|
||||
if in.ReplyTo != nil {
|
||||
var target model.Comment
|
||||
if err := model.DB.Where("id = ? AND post_id = ?", *in.ReplyTo, in.PostID).First(&target).Error; err != nil {
|
||||
return nil, ErrCommentNotFound
|
||||
}
|
||||
}
|
||||
|
||||
comment := &model.Comment{
|
||||
PostID: in.PostID,
|
||||
UserID: in.UserID,
|
||||
Floor: maxFloor + 1,
|
||||
Content: content,
|
||||
ReplyTo: in.ReplyTo,
|
||||
GuestNick: strings.TrimSpace(in.GuestNick),
|
||||
GuestEmail: strings.TrimSpace(in.GuestEmail),
|
||||
GuestURL: strings.TrimSpace(in.GuestURL),
|
||||
IsPrivate: in.IsPrivate,
|
||||
}
|
||||
return comment, model.DB.Create(comment).Error
|
||||
}
|
||||
|
||||
func (s *CommentService) Delete(userID, commentID uint, isAdmin bool) error {
|
||||
var comment model.Comment
|
||||
if err := model.DB.First(&comment, commentID).Error; err != nil {
|
||||
return ErrCommentNotFound
|
||||
}
|
||||
if !isAdmin && comment.UserID != userID {
|
||||
return ErrPermissionDenied
|
||||
}
|
||||
return model.DB.Delete(&comment).Error
|
||||
}
|
||||
|
||||
func (s *CommentService) AdminDelete(commentID uint) error {
|
||||
return model.DB.Delete(&model.Comment{}, commentID).Error
|
||||
}
|
||||
|
||||
// ListRecent 管理员查看最近评论
|
||||
func (s *CommentService) ListRecent(page, size int) ([]model.Comment, int64, error) {
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if size < 1 {
|
||||
size = 20
|
||||
}
|
||||
var total int64
|
||||
model.DB.Model(&model.Comment{}).Count(&total)
|
||||
var comments []model.Comment
|
||||
err := model.DB.Preload("User").Preload("Post").
|
||||
Order("id desc").Offset((page-1)*size).Limit(size).Find(&comments).Error
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
s.fillReplyTargets(comments, true)
|
||||
return comments, total, err
|
||||
}
|
||||
Reference in New Issue
Block a user