From bc4596721ee2c2b9df31f4304fa1f979a45fe533 Mon Sep 17 00:00:00 2001 From: Michael Li Date: Fri, 30 Dec 2022 18:21:06 +0800 Subject: [PATCH] mir: add priv api implement full implement for new web service --- auto/api/v1/priv.go | 90 +++++++--- internal/model/web/priv.go | 29 ++++ internal/servants/web/priv.go | 310 +++++++++++++++++++++++++++++++++- mirc/web/v1/priv.go | 8 +- 4 files changed, 408 insertions(+), 29 deletions(-) diff --git a/auto/api/v1/priv.go b/auto/api/v1/priv.go index 29b39a92..8e9b4856 100644 --- a/auto/api/v1/priv.go +++ b/auto/api/v1/priv.go @@ -14,10 +14,10 @@ type Priv interface { // Chain provide handlers chain for gin Chain() gin.HandlersChain - DeleteCommentReply() mir.Error - CreateCommentReply() mir.Error - DeleteComment() mir.Error - CreateComment() mir.Error + DeleteCommentReply(*web.DeleteCommentReplyReq) mir.Error + CreateCommentReply(*web.CreateCommentReplyReq) (*web.CreateCommentReplyResp, mir.Error) + DeleteComment(*web.DeleteCommentReq) mir.Error + CreateComment(*web.CreateCommentReq) (*web.CreateCommentResp, mir.Error) VisiblePost(*web.VisiblePostReq) (*web.VisiblePostResp, mir.Error) StickTweet(*web.StickTweetReq) (*web.StickTweetResp, mir.Error) LockTweet(*web.LockTweetReq) (*web.LockTweetResp, mir.Error) @@ -33,6 +33,10 @@ type Priv interface { } type PrivBinding interface { + BindDeleteCommentReply(*gin.Context) (*web.DeleteCommentReplyReq, mir.Error) + BindCreateCommentReply(*gin.Context) (*web.CreateCommentReplyReq, mir.Error) + BindDeleteComment(*gin.Context) (*web.DeleteCommentReq, mir.Error) + BindCreateComment(*gin.Context) (*web.CreateCommentReq, mir.Error) BindVisiblePost(*gin.Context) (*web.VisiblePostReq, mir.Error) BindStickTweet(*gin.Context) (*web.StickTweetReq, mir.Error) BindLockTweet(*gin.Context) (*web.LockTweetReq, mir.Error) @@ -49,9 +53,9 @@ type PrivBinding interface { type PrivRender interface { RenderDeleteCommentReply(*gin.Context, mir.Error) - RenderCreateCommentReply(*gin.Context, mir.Error) + RenderCreateCommentReply(*gin.Context, *web.CreateCommentReplyResp, mir.Error) RenderDeleteComment(*gin.Context, mir.Error) - RenderCreateComment(*gin.Context, mir.Error) + RenderCreateComment(*gin.Context, *web.CreateCommentResp, mir.Error) RenderVisiblePost(*gin.Context, *web.VisiblePostResp, mir.Error) RenderStickTweet(*gin.Context, *web.StickTweetResp, mir.Error) RenderLockTweet(*gin.Context, *web.LockTweetResp, mir.Error) @@ -81,7 +85,12 @@ func RegisterPrivServant(e *gin.Engine, s Priv, b PrivBinding, r PrivRender) { default: } - r.RenderDeleteCommentReply(c, s.DeleteCommentReply()) + req, err := b.BindDeleteCommentReply(c) + if err != nil { + r.RenderDeleteCommentReply(c, err) + return + } + r.RenderDeleteCommentReply(c, s.DeleteCommentReply(req)) }) router.Handle("POST", "/post/comment/reply", func(c *gin.Context) { @@ -91,7 +100,13 @@ func RegisterPrivServant(e *gin.Engine, s Priv, b PrivBinding, r PrivRender) { default: } - r.RenderCreateCommentReply(c, s.CreateCommentReply()) + req, err := b.BindCreateCommentReply(c) + if err != nil { + r.RenderCreateCommentReply(c, nil, err) + return + } + resp, err := s.CreateCommentReply(req) + r.RenderCreateCommentReply(c, resp, err) }) router.Handle("DELETE", "/post/comment", func(c *gin.Context) { @@ -101,7 +116,12 @@ func RegisterPrivServant(e *gin.Engine, s Priv, b PrivBinding, r PrivRender) { default: } - r.RenderDeleteComment(c, s.DeleteComment()) + req, err := b.BindDeleteComment(c) + if err != nil { + r.RenderDeleteComment(c, err) + return + } + r.RenderDeleteComment(c, s.DeleteComment(req)) }) router.Handle("POST", "/post/comment", func(c *gin.Context) { @@ -111,7 +131,13 @@ func RegisterPrivServant(e *gin.Engine, s Priv, b PrivBinding, r PrivRender) { default: } - r.RenderCreateComment(c, s.CreateComment()) + req, err := b.BindCreateComment(c) + if err != nil { + r.RenderCreateComment(c, nil, err) + return + } + resp, err := s.CreateComment(req) + r.RenderCreateComment(c, resp, err) }) router.Handle("POST", "/post/visibility", func(c *gin.Context) { @@ -283,20 +309,20 @@ func (UnimplementedPrivServant) Chain() gin.HandlersChain { return nil } -func (UnimplementedPrivServant) DeleteCommentReply() mir.Error { +func (UnimplementedPrivServant) DeleteCommentReply(req *web.DeleteCommentReplyReq) mir.Error { return mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) } -func (UnimplementedPrivServant) CreateCommentReply() mir.Error { - return mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) +func (UnimplementedPrivServant) CreateCommentReply(req *web.CreateCommentReplyReq) (*web.CreateCommentReplyResp, mir.Error) { + return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) } -func (UnimplementedPrivServant) DeleteComment() mir.Error { +func (UnimplementedPrivServant) DeleteComment(req *web.DeleteCommentReq) mir.Error { return mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) } -func (UnimplementedPrivServant) CreateComment() mir.Error { - return mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) +func (UnimplementedPrivServant) CreateComment(req *web.CreateCommentReq) (*web.CreateCommentResp, mir.Error) { + return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) } func (UnimplementedPrivServant) VisiblePost(req *web.VisiblePostReq) (*web.VisiblePostResp, mir.Error) { @@ -350,16 +376,16 @@ func (r *UnimplementedPrivRender) RenderDeleteCommentReply(c *gin.Context, err m r.RenderAny(c, nil, err) } -func (r *UnimplementedPrivRender) RenderCreateCommentReply(c *gin.Context, err mir.Error) { - r.RenderAny(c, nil, err) +func (r *UnimplementedPrivRender) RenderCreateCommentReply(c *gin.Context, data *web.CreateCommentReplyResp, err mir.Error) { + r.RenderAny(c, data, err) } func (r *UnimplementedPrivRender) RenderDeleteComment(c *gin.Context, err mir.Error) { r.RenderAny(c, nil, err) } -func (r *UnimplementedPrivRender) RenderCreateComment(c *gin.Context, err mir.Error) { - r.RenderAny(c, nil, err) +func (r *UnimplementedPrivRender) RenderCreateComment(c *gin.Context, data *web.CreateCommentResp, err mir.Error) { + r.RenderAny(c, data, err) } func (r *UnimplementedPrivRender) RenderVisiblePost(c *gin.Context, data *web.VisiblePostResp, err mir.Error) { @@ -409,6 +435,30 @@ type UnimplementedPrivBinding struct { BindAny func(*gin.Context, any) mir.Error } +func (b *UnimplementedPrivBinding) BindDeleteCommentReply(c *gin.Context) (*web.DeleteCommentReplyReq, mir.Error) { + obj := new(web.DeleteCommentReplyReq) + err := b.BindAny(c, obj) + return obj, err +} + +func (b *UnimplementedPrivBinding) BindCreateCommentReply(c *gin.Context) (*web.CreateCommentReplyReq, mir.Error) { + obj := new(web.CreateCommentReplyReq) + err := b.BindAny(c, obj) + return obj, err +} + +func (b *UnimplementedPrivBinding) BindDeleteComment(c *gin.Context) (*web.DeleteCommentReq, mir.Error) { + obj := new(web.DeleteCommentReq) + err := b.BindAny(c, obj) + return obj, err +} + +func (b *UnimplementedPrivBinding) BindCreateComment(c *gin.Context) (*web.CreateCommentReq, mir.Error) { + obj := new(web.CreateCommentReq) + err := b.BindAny(c, obj) + return obj, err +} + func (b *UnimplementedPrivBinding) BindVisiblePost(c *gin.Context) (*web.VisiblePostReq, mir.Error) { obj := new(web.VisiblePostReq) err := b.BindAny(c, obj) diff --git a/internal/model/web/priv.go b/internal/model/web/priv.go index 290f80bd..9a592141 100644 --- a/internal/model/web/priv.go +++ b/internal/model/web/priv.go @@ -81,6 +81,35 @@ type VisiblePostResp struct { Visibility core.PostVisibleT `json:"visibility"` } +type CreateCommentReq struct { + SimpleInfo `json:"-" binding:"-"` + PostID int64 `json:"post_id" binding:"required"` + Contents []*PostContentItem `json:"contents" binding:"required"` + Users []string `json:"users" binding:"required"` + ClientIP string `json:"-" binding:"-"` +} + +type CreateCommentResp core.Comment + +type CreateCommentReplyReq struct { + SimpleInfo `json:"-" binding:"-"` + CommentID int64 `json:"comment_id" binding:"required"` + Content string `json:"content" binding:"required"` + AtUserID int64 `json:"at_user_id"` + ClientIP string `json:"-" binding:"-"` +} + +type CreateCommentReplyResp core.CommentReply + +type DeleteCommentReq struct { + BaseInfo `json:"-" binding:"-"` + ID int64 `json:"id" binding:"required"` +} +type DeleteCommentReplyReq struct { + BaseInfo `json:"-" binding:"-"` + ID int64 `json:"id" binding:"required"` +} + type UploadAttachmentReq struct { SimpleInfo `json:"-" binding:"-"` UploadType string diff --git a/internal/servants/web/priv.go b/internal/servants/web/priv.go index 87bc95dd..f62cc1da 100644 --- a/internal/servants/web/priv.go +++ b/internal/servants/web/priv.go @@ -7,12 +7,14 @@ package web import ( "image" "strings" + "time" "github.com/alimy/mir/v3" "github.com/disintegration/imaging" "github.com/gin-gonic/gin" "github.com/gofrs/uuid" api "github.com/rocboss/paopao-ce/auto/api/v1" + "github.com/rocboss/paopao-ce/internal/conf" "github.com/rocboss/paopao-ce/internal/core" "github.com/rocboss/paopao-ce/internal/model/web" "github.com/rocboss/paopao-ce/internal/servants/base" @@ -34,6 +36,8 @@ var ( "public/video": core.AttachmentTypeVideo, "attachment": core.AttachmentTypeOther, } + + _MaxCommentCount = conf.AppSetting.MaxCommentCount ) type privSrv struct { @@ -53,24 +57,33 @@ type privRender struct { *api.UnimplementedPrivRender } -func (b *privBinding) BindUploadAttachment(c *gin.Context) (*web.UploadAttachmentReq, mir.Error) { +func (b *privBinding) BindUploadAttachment(c *gin.Context) (_ *web.UploadAttachmentReq, xerr mir.Error) { UserId, exist := base.UserIdFrom(c) if !exist { return nil, xerror.UnauthorizedAuthNotExist } + uploadType := c.Request.FormValue("type") file, fileHeader, err := c.Request.FormFile("file") if err != nil { return nil, _errFileUploadFailed } + defer func() { + if xerr != nil { + file.Close() + } + }() + if err := fileCheck(uploadType, fileHeader.Size); err != nil { return nil, err } + contentType := fileHeader.Header.Get("Content-Type") fileExt, xerr := getFileExt(contentType) if xerr != nil { return nil, xerr } + return &web.UploadAttachmentReq{ SimpleInfo: web.SimpleInfo{ Uid: UserId, @@ -116,6 +129,20 @@ func (s *privBinding) BindCreateTweet(c *gin.Context) (*web.CreateTweetReq, mir. return v, err } +func (s *privBinding) BindCreateCommentReply(c *gin.Context) (*web.CreateCommentReplyReq, mir.Error) { + v := &web.CreateCommentReplyReq{} + err := s.BindAny(c, v) + v.ClientIP = c.ClientIP() + return v, err +} + +func (s *privBinding) BindCreateComment(c *gin.Context) (*web.CreateCommentReq, mir.Error) { + v := &web.CreateCommentReq{} + err := s.BindAny(c, v) + v.ClientIP = c.ClientIP() + return v, err +} + func (s *privSrv) Chain() gin.HandlersChain { return gin.HandlersChain{chain.JWT(), chain.Priv()} } @@ -168,7 +195,7 @@ func (s *privSrv) DownloadAttachmentPrecheck(req *web.DownloadAttachmentPrecheck logrus.Errorf("Ds.GetPostContentByID err: %s", err) return nil, _errInvalidDownloadReq } - resp := &web.DownloadAttachmentPrecheckResp{true} + resp := &web.DownloadAttachmentPrecheckResp{Paid: true} if content.Type == core.ContentTypeChargeAttachment { tweet, err := s.GetTweetBy(content.PostID) if err != nil { @@ -310,8 +337,7 @@ func (s *privSrv) CreateTweet(req *web.CreateTweetReq) (_ *web.CreateTweetResp, } } // 推送Search - go s.PushPostToSearch(post) - + s.PushPostToSearch(post) formatedPosts, err := s.Ds.RevampPosts([]*core.PostFormated{post.Format()}) if err != nil { logrus.Infof("Ds.RevampPosts err: %s", err) @@ -348,6 +374,223 @@ func (s *privSrv) DeleteTweet(req *web.DeleteTweetReq) mir.Error { return nil } +func (s *privSrv) DeleteCommentReply(req *web.DeleteCommentReplyReq) mir.Error { + reply, err := s.Ds.GetCommentReplyByID(req.ID) + if err != nil { + logrus.Errorf("Ds.GetCommentReplyByID err: %s", err) + return _errGetReplyFailed + } + if req.User.ID != reply.UserID && !req.User.IsAdmin { + return _errNoPermission + } + // 执行删除 + err = s.deletePostCommentReply(reply) + if err != nil { + logrus.Errorf("s.deletePostCommentReply err: %s", err) + return _errDeleteCommentFailed + } + return nil +} + +func (s *privSrv) CreateCommentReply(req *web.CreateCommentReplyReq) (*web.CreateCommentReplyResp, mir.Error) { + var ( + post *core.Post + comment *core.Comment + atUserID int64 + err error + ) + + if post, comment, atUserID, err = s.createPostPreHandler(req.CommentID, req.Uid, req.AtUserID); err != nil { + return nil, _errCreateReplyFailed + } + + // 创建评论 + reply := &core.CommentReply{ + CommentID: req.CommentID, + UserID: req.Uid, + Content: req.Content, + AtUserID: atUserID, + IP: req.ClientIP, + IPLoc: util.GetIPLoc(req.ClientIP), + } + + reply, err = s.Ds.CreateCommentReply(reply) + if err != nil { + return nil, _errCreateReplyFailed + } + + // 更新Post回复数 + post.CommentCount++ + post.LatestRepliedOn = time.Now().Unix() + s.Ds.UpdatePost(post) + + // 更新索引 + s.PushPostToSearch(post) + + // 创建用户消息提醒 + commentMaster, err := s.Ds.GetUserByID(comment.UserID) + if err == nil && commentMaster.ID != req.Uid { + go s.Ds.CreateMessage(&core.Message{ + SenderUserID: req.Uid, + ReceiverUserID: commentMaster.ID, + Type: core.MsgTypeReply, + Brief: "在泡泡评论下回复了你", + PostID: post.ID, + CommentID: comment.ID, + ReplyID: reply.ID, + }) + } + postMaster, err := s.Ds.GetUserByID(post.UserID) + if err == nil && postMaster.ID != req.Uid && commentMaster.ID != postMaster.ID { + go s.Ds.CreateMessage(&core.Message{ + SenderUserID: req.Uid, + ReceiverUserID: postMaster.ID, + Type: core.MsgTypeReply, + Brief: "在泡泡评论下发布了新回复", + PostID: post.ID, + CommentID: comment.ID, + ReplyID: reply.ID, + }) + } + if atUserID > 0 { + user, err := s.Ds.GetUserByID(atUserID) + if err == nil && user.ID != req.Uid && commentMaster.ID != user.ID && postMaster.ID != user.ID { + // 创建消息提醒 + go s.Ds.CreateMessage(&core.Message{ + SenderUserID: req.Uid, + ReceiverUserID: user.ID, + Type: core.MsgTypeReply, + Brief: "在泡泡评论的回复中@了你", + PostID: post.ID, + CommentID: comment.ID, + ReplyID: reply.ID, + }) + } + } + return (*web.CreateCommentReplyResp)(reply), nil +} + +func (s *privSrv) DeleteComment(req *web.DeleteCommentReq) mir.Error { + comment, err := s.Ds.GetCommentByID(req.ID) + if err != nil { + logrus.Errorf("Ds.GetCommentByID err: %v\n", err) + return _errGetCommentFailed + } + if req.User.ID != comment.UserID && !req.User.IsAdmin { + return _errNoPermission + } + // 加载post + post, err := s.Ds.GetPostByID(comment.PostID) + if err != nil { + return _errDeleteCommentFailed + } + // 更新post回复数 + post.CommentCount-- + if err := s.Ds.UpdatePost(post); err != nil { + logrus.Errorf("Ds.UpdatePost err: %s", err) + return _errDeleteCommentFailed + } + // TODO: 优化删除逻辑,事务化删除comment + if err := s.Ds.DeleteComment(comment); err != nil { + logrus.Errorf("Ds.DeleteComment err: %s", err) + return _errDeleteCommentFailed + } + return nil +} + +func (s *privSrv) CreateComment(req *web.CreateCommentReq) (_ *web.CreateCommentResp, xerr mir.Error) { + var ( + mediaContents []string + err error + ) + defer func() { + if xerr != nil { + deleteOssObjects(s.oss, mediaContents) + } + }() + + if mediaContents, err = persistMediaContents(s.oss, req.Contents); err != nil { + return nil, xerror.ServerError + } + + // 加载Post + post, err := s.Ds.GetPostByID(req.PostID) + if err != nil { + logrus.Errorf("Ds.GetPostByID err:%s", err) + return nil, xerror.ServerError + } + if post.CommentCount >= _MaxCommentCount { + return nil, _errMaxCommentCount + } + comment := &core.Comment{ + PostID: post.ID, + UserID: req.Uid, + IP: req.ClientIP, + IPLoc: util.GetIPLoc(req.ClientIP), + } + comment, err = s.Ds.CreateComment(comment) + if err != nil { + logrus.Errorf("Ds.CreateComment err:%s", err) + return nil, _errCreateCommentFailed + } + + for _, item := range req.Contents { + // 检查附件是否是本站资源 + if item.Type == core.ContentTypeImage || item.Type == core.ContentTypeVideo || item.Type == core.ContentTypeAttachment { + if err := s.Ds.CheckAttachment(item.Content); err != nil { + continue + } + } + postContent := &core.CommentContent{ + CommentID: comment.ID, + UserID: req.Uid, + Content: item.Content, + Type: item.Type, + Sort: item.Sort, + } + s.Ds.CreateCommentContent(postContent) + } + + // 更新Post回复数 + post.CommentCount++ + post.LatestRepliedOn = time.Now().Unix() + s.Ds.UpdatePost(post) + + // 更新索引 + s.PushPostToSearch(post) + + // 创建用户消息提醒 + postMaster, err := s.Ds.GetUserByID(post.UserID) + if err == nil && postMaster.ID != req.Uid { + go s.Ds.CreateMessage(&core.Message{ + SenderUserID: req.Uid, + ReceiverUserID: postMaster.ID, + Type: core.MsgtypeComment, + Brief: "在泡泡中评论了你", + PostID: post.ID, + CommentID: comment.ID, + }) + } + for _, u := range req.Users { + user, err := s.Ds.GetUserByUsername(u) + if err != nil || user.ID == req.Uid || user.ID == postMaster.ID { + continue + } + + // 创建消息提醒 + go s.Ds.CreateMessage(&core.Message{ + SenderUserID: req.Uid, + ReceiverUserID: user.ID, + Type: core.MsgtypeComment, + Brief: "在泡泡评论中@了你", + PostID: post.ID, + CommentID: comment.ID, + }) + } + + return (*web.CreateCommentResp)(comment), nil +} + func (s *privSrv) CollectionTweet(req *web.CollectionTweetReq) (*web.CollectionTweetResp, mir.Error) { status := false collection, err := s.Ds.GetUserPostCollection(req.ID, req.Uid) @@ -406,7 +649,7 @@ func (s *privSrv) VisiblePost(req *web.VisiblePostReq) (*web.VisiblePostResp, mi // 推送Search post.Visibility = req.Visibility - go s.PushPostToSearch(post) + s.PushPostToSearch(post) return &web.VisiblePostResp{ Visibility: req.Visibility, @@ -449,6 +692,63 @@ func (s *privSrv) LockTweet(req *web.LockTweetReq) (*web.LockTweetResp, mir.Erro }, nil } +func (s *privSrv) deletePostCommentReply(reply *core.CommentReply) error { + err := s.Ds.DeleteCommentReply(reply) + if err != nil { + return err + } + // 加载Comment + comment, err := s.Ds.GetCommentByID(reply.CommentID) + if err != nil { + return err + } + // 加载comment的post + post, err := s.Ds.GetPostByID(comment.PostID) + if err != nil { + return err + } + // 更新Post回复数 + post.CommentCount-- + post.LatestRepliedOn = time.Now().Unix() + s.Ds.UpdatePost(post) + // 更新索引 + s.PushPostToSearch(post) + return nil +} + +func (s *privSrv) createPostPreHandler(commentID int64, userID, atUserID int64) (*core.Post, *core.Comment, int64, + error) { + // 加载Comment + comment, err := s.Ds.GetCommentByID(commentID) + if err != nil { + return nil, nil, atUserID, err + } + + // 加载comment的post + post, err := s.Ds.GetPostByID(comment.PostID) + if err != nil { + return nil, nil, atUserID, err + } + + if post.CommentCount >= _MaxCommentCount { + return nil, nil, atUserID, _errMaxCommentCount + } + + if userID == atUserID { + atUserID = 0 + } + + if atUserID > 0 { + // 检测目前用户是否存在 + users, _ := s.Ds.GetUsersByIDs([]int64{atUserID}) + if len(users) == 0 { + atUserID = 0 + } + } + + return post, comment, atUserID, nil +} + func (s *privSrv) createPostStar(postID, userID int64) (*core.PostStar, mir.Error) { // 加载Post post, err := s.Ds.GetPostByID(postID) diff --git a/mirc/web/v1/priv.go b/mirc/web/v1/priv.go index f06d6c8d..e9737e02 100644 --- a/mirc/web/v1/priv.go +++ b/mirc/web/v1/priv.go @@ -46,14 +46,14 @@ type Priv struct { VisiblePost func(Post, web.VisiblePostReq) web.VisiblePostResp `mir:"/post/visibility"` // CreateTweetComment 发布动态评论 - CreateComment func(Post) `mir:"/post/comment"` + CreateComment func(Post, web.CreateCommentReq) web.CreateCommentResp `mir:"/post/comment"` // DeletePostComment 删除动态评论 - DeleteComment func(Delete) `mir:"/post/comment"` + DeleteComment func(Delete, web.DeleteCommentReq) `mir:"/post/comment"` // CreateCommentReply 发布评论回复 - CreateCommentReply func(Post) `mir:"/post/comment/reply"` + CreateCommentReply func(Post, web.CreateCommentReplyReq) web.CreateCommentReplyResp `mir:"/post/comment/reply"` // DeleteCommentReply 删除评论回复 - DeleteCommentReply func(Delete) `mir:"/post/comment/reply"` + DeleteCommentReply func(Delete, web.DeleteCommentReplyReq) `mir:"/post/comment/reply"` }