migrate from github

This commit is contained in:
2026-07-15 22:42:31 +08:00
commit 3ad559f05e
262 changed files with 21637 additions and 0 deletions
@@ -0,0 +1,257 @@
package comment_service
import (
"context"
"encoding/json"
"strings"
"github.com/XingfenD/yoresee_doc/internal/constant"
"github.com/XingfenD/yoresee_doc/internal/domain_event"
"github.com/XingfenD/yoresee_doc/internal/dto"
"github.com/XingfenD/yoresee_doc/internal/model"
"github.com/XingfenD/yoresee_doc/internal/repository"
"github.com/XingfenD/yoresee_doc/internal/repository/comment_repo"
"github.com/XingfenD/yoresee_doc/internal/repository/document_repo"
"github.com/XingfenD/yoresee_doc/internal/repository/knowledge_base_repo"
"github.com/XingfenD/yoresee_doc/internal/repository/user_repo"
"github.com/XingfenD/yoresee_doc/internal/status"
"github.com/XingfenD/yoresee_doc/internal/utils"
"github.com/sirupsen/logrus"
)
type CommentService struct {
commentRepo *comment_repo.CommentRepository
documentRepo *document_repo.DocumentRepository
userRepo *user_repo.UserRepository
kbRepo *knowledge_base_repo.KnowledgeBaseRepository
}
func NewCommentService(repos *repository.Repositories) *CommentService {
return &CommentService{
commentRepo: repos.Comment,
documentRepo: repos.Document,
userRepo: repos.User,
kbRepo: repos.KnowledgeBase,
}
}
func (s *CommentService) CreateComment(req *dto.CreateCommentRequest) (*model.DocumentComment, error) {
if req == nil || strings.TrimSpace(req.DocumentExternalID) == "" || strings.TrimSpace(req.Content) == "" {
return nil, status.StatusParamError
}
if strings.TrimSpace(req.CreatorExternalID) == "" {
return nil, status.StatusTokenInvalid
}
doc, err := s.documentRepo.GetByExternalID(req.DocumentExternalID).Exec(context.Background())
if err != nil || doc == nil {
return nil, status.StatusDocumentNotFound
}
creatorID, err := s.userRepo.GetIDByExternalID(req.CreatorExternalID).Exec()
if err != nil {
return nil, status.StatusUserNotFound
}
parentID := int64(0)
var parentComment *model.DocumentComment
if req.ParentExternalID != nil && strings.TrimSpace(*req.ParentExternalID) != "" {
parent, err := s.commentRepo.GetByExternalID(*req.ParentExternalID).Exec()
if err != nil || parent == nil {
return nil, status.StatusReadDBError
}
parentID = parent.ID
parentComment = parent
}
item := &model.DocumentComment{
ExternalID: utils.GenerateExternalID(utils.ExternalIDContextComment),
DocumentID: doc.ID,
ParentID: parentID,
CreatorID: creatorID,
Content: req.Content,
}
if req.AnchorID != nil && strings.TrimSpace(*req.AnchorID) != "" {
item.AnchorID = strings.TrimSpace(*req.AnchorID)
} else if parentComment != nil && strings.TrimSpace(parentComment.AnchorID) != "" {
item.AnchorID = parentComment.AnchorID
}
if strings.TrimSpace(item.AnchorID) == "" {
return nil, status.StatusParamError
}
if err := s.commentRepo.Create(item).Exec(); err != nil {
return nil, status.StatusWriteDBError
}
notifyQuote := ""
if req.Quote != nil {
notifyQuote = strings.TrimSpace(*req.Quote)
}
s.notifyCommentTargets(doc, item, parentComment, notifyQuote, req.MentionedUserExternalIDs)
return item, nil
}
func (s *CommentService) ListComments(req *dto.ListCommentsRequest) ([]model.DocumentComment, int64, error) {
if req == nil || strings.TrimSpace(req.DocumentExternalID) == "" {
return nil, 0, status.StatusParamError
}
doc, err := s.documentRepo.GetByExternalID(req.DocumentExternalID).Exec(context.Background())
if err != nil || doc == nil {
return nil, 0, status.StatusDocumentNotFound
}
op := s.commentRepo.ListByDocument(doc.ID).
WithPagination(req.Page, req.PageSize)
list, total, err := op.ExecWithTotal()
if err != nil {
logrus.Errorf("[Service layer: CommentService] list comments failed, document_external_id=%s, err=%+v", req.DocumentExternalID, err)
return nil, 0, status.GenErrWithCustomMsg(status.StatusReadDBError, "list comments failed")
}
return list, total, nil
}
func (s *CommentService) DeleteComment(req *dto.DeleteCommentRequest, isAdmin bool) error {
if req == nil || strings.TrimSpace(req.ExternalID) == "" {
return status.StatusParamError
}
comment, err := s.commentRepo.GetByExternalID(req.ExternalID).Exec()
if err != nil || comment == nil {
return status.StatusReadDBError
}
if !isAdmin {
operatorID, err := s.userRepo.GetIDByExternalID(req.OperatorExternalID).Exec()
if err != nil {
return status.StatusUserNotFound
}
if operatorID != comment.CreatorID {
return status.StatusPermissionDenied
}
}
if err := s.commentRepo.Delete(comment.ID).Exec(); err != nil {
return status.StatusWriteDBError
}
return nil
}
func (s *CommentService) UpdateComment(req *dto.UpdateCommentRequest, isAdmin bool) (*model.DocumentComment, error) {
if req == nil || strings.TrimSpace(req.ExternalID) == "" || strings.TrimSpace(req.Content) == "" {
return nil, status.StatusParamError
}
comment, err := s.commentRepo.GetByExternalID(req.ExternalID).Exec()
if err != nil || comment == nil {
return nil, status.StatusReadDBError
}
if !isAdmin {
operatorID, err := s.userRepo.GetIDByExternalID(req.OperatorExternalID).Exec()
if err != nil {
return nil, status.StatusUserNotFound
}
if operatorID != comment.CreatorID {
return nil, status.StatusPermissionDenied
}
}
if err := s.commentRepo.UpdateContentByID(comment.ID, req.Content).Exec(); err != nil {
return nil, status.StatusWriteDBError
}
comment.Content = req.Content
return comment, nil
}
var CommentSvc *CommentService
func (s *CommentService) notifyCommentTargets(doc *model.Document, comment *model.DocumentComment, parent *model.DocumentComment, quote string, mentionedUserExternalIDs []string) {
if doc == nil || comment == nil {
return
}
// Resolve knowledge base external ID for navigation payload
kbExternalID := ""
if doc.KnowledgeID != nil && *doc.KnowledgeID != 0 {
kb, err := s.kbRepo.GetByID(*doc.KnowledgeID).Exec()
if err == nil && kb != nil {
kbExternalID = kb.ExternalID
}
}
parentExternalID := ""
if parent != nil && parent.ID != 0 {
parentExternalID = parent.ExternalID
}
payloadBase := map[string]any{
"document_external_id": doc.ExternalID,
"comment_external_id": comment.ExternalID,
"document_title": doc.Title,
"parent_comment_external_id": parentExternalID,
"knowledge_base_external_id": kbExternalID,
"mentioned_user_external_ids": mentionedUserExternalIDs,
}
payloadJSON, _ := json.Marshal(payloadBase)
payloadStr := string(payloadJSON)
publish := func(receivers []string, notifType, title string) {
if len(receivers) == 0 {
return
}
evt := domain_event.NotificationCreateEvent{
ReceiverExternalIDs: receivers,
Type: notifType,
Title: title,
Content: comment.Content,
PayloadJSON: payloadStr,
}
if err := domain_event.PublishNotificationCreateEvent(context.Background(), evt); err != nil {
logrus.Errorf("publish %s notification failed: %v", notifType, err)
}
}
// Build mention receiver set (highest priority — skip for other types)
mentionSet := map[string]struct{}{}
for _, id := range mentionedUserExternalIDs {
id = strings.TrimSpace(id)
if id != "" && id != comment.ExternalID {
mentionSet[id] = struct{}{}
}
}
// Build reply receivers (parent comment creator, excluding commenter and mention receivers)
var replyReceivers []string
if parent != nil && parent.ID != 0 {
parentCreator, err := s.userRepo.GetByID(parent.CreatorID).Exec()
if err == nil && parentCreator != nil {
id := strings.TrimSpace(parentCreator.ExternalID)
if id != "" && parentCreator.ID != comment.CreatorID {
if _, isMentioned := mentionSet[id]; !isMentioned {
replyReceivers = append(replyReceivers, id)
}
}
}
}
// Build comment receivers (document owner, excluding commenter, reply receivers, and mention receivers)
replySet := map[string]struct{}{}
for _, id := range replyReceivers {
replySet[id] = struct{}{}
}
var commentReceivers []string
if doc.UserID != comment.CreatorID {
owner, err := s.userRepo.GetByID(doc.UserID).Exec()
if err == nil && owner != nil {
id := strings.TrimSpace(owner.ExternalID)
if id != "" {
if _, isMentioned := mentionSet[id]; !isMentioned {
if _, isReply := replySet[id]; !isReply {
commentReceivers = append(commentReceivers, id)
}
}
}
}
}
mentionReceivers := make([]string, 0, len(mentionSet))
for id := range mentionSet {
mentionReceivers = append(mentionReceivers, id)
}
publish(mentionReceivers, constant.NotificationType_Mention, constant.GetNotificationTitle(constant.NotificationType_Mention))
publish(replyReceivers, constant.NotificationType_Reply, constant.GetNotificationTitle(constant.NotificationType_Reply))
publish(commentReceivers, constant.NotificationType_Comment, constant.GetNotificationTitle(constant.NotificationType_Comment))
}