223 lines
6.2 KiB
Go
223 lines
6.2 KiB
Go
|
|
package service
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"fmt"
|
||
|
|
"log"
|
||
|
|
"slices"
|
||
|
|
|
||
|
|
"with_you/internal/dto"
|
||
|
|
"with_you/internal/model"
|
||
|
|
"with_you/internal/repository"
|
||
|
|
)
|
||
|
|
|
||
|
|
type PostRefService interface {
|
||
|
|
ProcessPostReferences(ctx context.Context, sourcePostID string, segments model.MessageSegments) error
|
||
|
|
EnrichPostRefSegments(ctx context.Context, segments model.MessageSegments, currentUserID string) (model.MessageSegments, error)
|
||
|
|
RecordClick(ctx context.Context, sourcePostID, targetPostID string) error
|
||
|
|
HandlePostDeleted(ctx context.Context, postID string) error
|
||
|
|
HandlePostRecovered(ctx context.Context, postID string) error
|
||
|
|
HandlePostHidden(ctx context.Context, postID string) error
|
||
|
|
GetRelatedPosts(ctx context.Context, postID string, page, pageSize int) ([]*model.Post, int64, error)
|
||
|
|
GetReferencedPosts(ctx context.Context, postID string) ([]*model.Post, error)
|
||
|
|
SuggestPosts(ctx context.Context, keyword string, limit int) ([]dto.PostRefBrief, error)
|
||
|
|
}
|
||
|
|
|
||
|
|
type postRefServiceImpl struct {
|
||
|
|
refRepo repository.PostRefRepository
|
||
|
|
postRepo repository.PostRepository
|
||
|
|
}
|
||
|
|
|
||
|
|
func NewPostRefService(refRepo repository.PostRefRepository, postRepo repository.PostRepository) PostRefService {
|
||
|
|
return &postRefServiceImpl{
|
||
|
|
refRepo: refRepo,
|
||
|
|
postRepo: postRepo,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *postRefServiceImpl) ProcessPostReferences(ctx context.Context, sourcePostID string, segments model.MessageSegments) error {
|
||
|
|
targetIDs := dto.ExtractReferencedPosts(segments)
|
||
|
|
if len(targetIDs) == 0 {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
validIDs := make([]string, 0, len(targetIDs))
|
||
|
|
for _, tid := range targetIDs {
|
||
|
|
post, err := s.postRepo.GetByID(tid)
|
||
|
|
if err != nil {
|
||
|
|
log.Printf("[WARN] post_ref target %s not found for source %s: %v", tid, sourcePostID, err)
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
if post.Status == model.PostStatusPublished && !post.IsDeleted {
|
||
|
|
validIDs = append(validIDs, tid)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if len(validIDs) == 0 {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
return s.refRepo.BatchUpsert(ctx, sourcePostID, validIDs)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *postRefServiceImpl) EnrichPostRefSegments(ctx context.Context, segments model.MessageSegments, currentUserID string) (model.MessageSegments, error) {
|
||
|
|
postIDs := dto.ExtractReferencedPosts(segments)
|
||
|
|
if len(postIDs) == 0 {
|
||
|
|
return segments, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
posts, err := s.postRepo.GetByIDs(postIDs)
|
||
|
|
if err != nil {
|
||
|
|
log.Printf("[WARN] failed to get referenced posts: %v", err)
|
||
|
|
return segments, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
postMap := make(map[string]*model.Post, len(posts))
|
||
|
|
for _, p := range posts {
|
||
|
|
postMap[p.ID] = p
|
||
|
|
}
|
||
|
|
|
||
|
|
enriched := make(model.MessageSegments, len(segments))
|
||
|
|
for i, seg := range segments {
|
||
|
|
enriched[i] = seg
|
||
|
|
if seg.Type == string(dto.SegmentTypePostRef) {
|
||
|
|
postID, _ := seg.Data["post_id"].(string)
|
||
|
|
enriched[i] = s.enrichSegment(seg, postID, postMap, currentUserID)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return enriched, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *postRefServiceImpl) enrichSegment(seg model.MessageSegment, postID string, postMap map[string]*model.Post, currentUserID string) model.MessageSegment {
|
||
|
|
newSeg := model.MessageSegment{
|
||
|
|
Type: seg.Type,
|
||
|
|
Data: map[string]any{"post_id": postID},
|
||
|
|
}
|
||
|
|
|
||
|
|
post, ok := postMap[postID]
|
||
|
|
if !ok || post == nil {
|
||
|
|
newSeg.Data["status"] = "deleted"
|
||
|
|
newSeg.Data["title"] = "该帖子已删除"
|
||
|
|
newSeg.Data["accessible"] = false
|
||
|
|
return newSeg
|
||
|
|
}
|
||
|
|
|
||
|
|
if post.IsDeleted {
|
||
|
|
newSeg.Data["status"] = "deleted"
|
||
|
|
newSeg.Data["title"] = "该帖子已删除"
|
||
|
|
newSeg.Data["accessible"] = false
|
||
|
|
return newSeg
|
||
|
|
}
|
||
|
|
|
||
|
|
if post.Status != model.PostStatusPublished {
|
||
|
|
if currentUserID == post.UserID {
|
||
|
|
newSeg.Data["status"] = string(post.Status)
|
||
|
|
newSeg.Data["title"] = post.Title
|
||
|
|
newSeg.Data["accessible"] = true
|
||
|
|
} else {
|
||
|
|
newSeg.Data["status"] = "hidden"
|
||
|
|
newSeg.Data["title"] = "该帖子不可见"
|
||
|
|
newSeg.Data["accessible"] = false
|
||
|
|
}
|
||
|
|
return newSeg
|
||
|
|
}
|
||
|
|
|
||
|
|
newSeg.Data["status"] = "published"
|
||
|
|
newSeg.Data["title"] = post.Title
|
||
|
|
newSeg.Data["accessible"] = true
|
||
|
|
|
||
|
|
if post.User != nil {
|
||
|
|
newSeg.Data["author"] = map[string]any{
|
||
|
|
"id": post.User.ID,
|
||
|
|
"nickname": post.User.Nickname,
|
||
|
|
"avatar": post.User.Avatar,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return newSeg
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *postRefServiceImpl) RecordClick(ctx context.Context, sourcePostID, targetPostID string) error {
|
||
|
|
return s.refRepo.IncrementClickCount(ctx, sourcePostID, targetPostID)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *postRefServiceImpl) HandlePostDeleted(ctx context.Context, postID string) error {
|
||
|
|
return s.refRepo.MarkStatusByTargetPostID(ctx, postID, model.PostRefDead)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *postRefServiceImpl) HandlePostRecovered(ctx context.Context, postID string) error {
|
||
|
|
return s.refRepo.MarkStatusByTargetPostID(ctx, postID, model.PostRefActive)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *postRefServiceImpl) HandlePostHidden(ctx context.Context, postID string) error {
|
||
|
|
return s.refRepo.MarkStatusByTargetPostID(ctx, postID, model.PostRefHidden)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *postRefServiceImpl) GetRelatedPosts(ctx context.Context, postID string, page, pageSize int) ([]*model.Post, int64, error) {
|
||
|
|
refs, total, err := s.refRepo.GetSourcesByTargetPostID(ctx, postID, page, pageSize)
|
||
|
|
if err != nil {
|
||
|
|
return nil, 0, err
|
||
|
|
}
|
||
|
|
|
||
|
|
posts := make([]*model.Post, 0, len(refs))
|
||
|
|
for _, ref := range refs {
|
||
|
|
if ref.SourcePost != nil {
|
||
|
|
posts = append(posts, ref.SourcePost)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return posts, total, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *postRefServiceImpl) GetReferencedPosts(ctx context.Context, postID string) ([]*model.Post, error) {
|
||
|
|
refs, err := s.refRepo.GetTargetsBySourcePostID(ctx, postID)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
posts := make([]*model.Post, 0, len(refs))
|
||
|
|
for _, ref := range refs {
|
||
|
|
if ref.TargetPost != nil && ref.TargetPost.Status == model.PostStatusPublished && !ref.TargetPost.IsDeleted {
|
||
|
|
posts = append(posts, ref.TargetPost)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return posts, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *postRefServiceImpl) SuggestPosts(ctx context.Context, keyword string, limit int) ([]dto.PostRefBrief, error) {
|
||
|
|
if limit <= 0 {
|
||
|
|
limit = 10
|
||
|
|
}
|
||
|
|
|
||
|
|
posts, _, err := s.postRepo.Search(keyword, 1, limit)
|
||
|
|
if err != nil {
|
||
|
|
return nil, fmt.Errorf("search posts for suggest: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
result := make([]dto.PostRefBrief, 0, len(posts))
|
||
|
|
for _, p := range posts {
|
||
|
|
brief := dto.PostRefBrief{
|
||
|
|
ID: p.ID,
|
||
|
|
Title: p.Title,
|
||
|
|
}
|
||
|
|
if p.ChannelID != nil {
|
||
|
|
brief.Channel = *p.ChannelID
|
||
|
|
}
|
||
|
|
result = append(result, brief)
|
||
|
|
}
|
||
|
|
|
||
|
|
slices.SortFunc(result, func(a, b dto.PostRefBrief) int {
|
||
|
|
if len(a.Title) < len(b.Title) {
|
||
|
|
return -1
|
||
|
|
}
|
||
|
|
if len(a.Title) > len(b.Title) {
|
||
|
|
return 1
|
||
|
|
}
|
||
|
|
return 0
|
||
|
|
})
|
||
|
|
|
||
|
|
return result, nil
|
||
|
|
}
|