Add functionality to filter out posts from blocked users in post listing endpoints. Includes a new `IsBlockedBatch` method for efficient batch checking of blocked relationships and `is_blocked` field in API responses when viewing a blocked user's posts.
839 lines
24 KiB
Go
839 lines
24 KiB
Go
package handler
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"fmt"
|
||
"strconv"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
"gorm.io/gorm"
|
||
|
||
"with_you/internal/dto"
|
||
"with_you/internal/model"
|
||
"with_you/internal/pkg/cursor"
|
||
"with_you/internal/pkg/response"
|
||
"with_you/internal/service"
|
||
)
|
||
|
||
// PostHandler 帖子处理器
|
||
type PostHandler struct {
|
||
postService service.PostService
|
||
userService service.UserService
|
||
channelService service.ChannelService
|
||
}
|
||
|
||
// NewPostHandler 创建帖子处理器
|
||
func NewPostHandler(postService service.PostService, userService service.UserService, channelService service.ChannelService) *PostHandler {
|
||
return &PostHandler{
|
||
postService: postService,
|
||
userService: userService,
|
||
channelService: channelService,
|
||
}
|
||
}
|
||
|
||
// collectAuthorIDs 从帖子列表中收集去重的作者ID
|
||
func collectAuthorIDs(posts []*model.Post) []string {
|
||
seen := make(map[string]struct{})
|
||
ids := make([]string, 0, len(posts))
|
||
for _, p := range posts {
|
||
if _, ok := seen[p.UserID]; !ok {
|
||
seen[p.UserID] = struct{}{}
|
||
ids = append(ids, p.UserID)
|
||
}
|
||
}
|
||
return ids
|
||
}
|
||
|
||
// filterBlockedPosts 过滤掉被当前用户拉黑的作者发布的帖子
|
||
func (h *PostHandler) filterBlockedPosts(ctx context.Context, posts []*model.Post, currentUserID string) []*model.Post {
|
||
if currentUserID == "" || len(posts) == 0 {
|
||
return posts
|
||
}
|
||
|
||
authorIDs := collectAuthorIDs(posts)
|
||
blockedMap, err := h.userService.IsBlockedBatch(ctx, currentUserID, authorIDs)
|
||
if err != nil {
|
||
return posts
|
||
}
|
||
|
||
filtered := make([]*model.Post, 0, len(posts))
|
||
for _, p := range posts {
|
||
if blockedMap[p.UserID] {
|
||
continue
|
||
}
|
||
filtered = append(filtered, p)
|
||
}
|
||
return filtered
|
||
}
|
||
|
||
// channelMapForPosts 批量解析帖子中的频道 ID,用于填充响应里的 channel 名称
|
||
func (h *PostHandler) channelMapForPosts(posts []*model.Post) map[string]*model.Channel {
|
||
ids := dto.CollectPostChannelIDs(posts)
|
||
if len(ids) == 0 {
|
||
return nil
|
||
}
|
||
m, err := h.channelService.MapByIDs(ids)
|
||
if err != nil {
|
||
return nil
|
||
}
|
||
return m
|
||
}
|
||
|
||
// Create 创建帖子
|
||
func (h *PostHandler) Create(c *gin.Context) {
|
||
userID := c.GetString("user_id")
|
||
if userID == "" {
|
||
response.Unauthorized(c, "")
|
||
return
|
||
}
|
||
|
||
var req dto.CreatePostWithSegmentsRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
response.BadRequest(c, err.Error())
|
||
return
|
||
}
|
||
|
||
content := req.Content
|
||
segments := req.Segments
|
||
|
||
if len(segments) == 0 && content == "" {
|
||
response.BadRequest(c, "content or segments is required")
|
||
return
|
||
}
|
||
|
||
post, err := h.postService.Create(c.Request.Context(), userID, req.Title, content, segments, req.Images, req.ChannelID)
|
||
if err != nil {
|
||
var moderationErr *service.PostModerationRejectedError
|
||
if errors.As(err, &moderationErr) {
|
||
response.BadRequest(c, moderationErr.UserMessage())
|
||
return
|
||
}
|
||
response.InternalServerError(c, "failed to create post")
|
||
return
|
||
}
|
||
|
||
chMap := h.channelMapForPosts([]*model.Post{post})
|
||
response.Success(c, dto.ConvertPostToResponse(post, chMap, false, false))
|
||
}
|
||
|
||
// GetByID 获取帖子(不增加浏览量)
|
||
func (h *PostHandler) GetByID(c *gin.Context) {
|
||
id := c.Param("id")
|
||
|
||
post, err := h.postService.GetByID(c.Request.Context(), id)
|
||
if err != nil {
|
||
response.NotFound(c, "post not found")
|
||
return
|
||
}
|
||
|
||
// 非作者不可查看未发布内容
|
||
currentUserID := c.GetString("user_id")
|
||
if post.Status != model.PostStatusPublished && post.UserID != currentUserID {
|
||
response.NotFound(c, "post not found")
|
||
return
|
||
}
|
||
|
||
// 注意:不再自动增加浏览量,浏览量通过 RecordView 端点单独记录
|
||
|
||
// 获取交互状态
|
||
isLiked, isFavorited := h.postService.GetPostInteractionStatusSingle(c.Request.Context(), id, currentUserID)
|
||
|
||
var authorWithFollowStatus *dto.UserResponse
|
||
if post.User != nil {
|
||
if currentUserID != "" {
|
||
_, isFollowing, isFollowingMe, err := h.userService.GetUserByIDWithMutualFollowStatus(c.Request.Context(), post.UserID, currentUserID)
|
||
if err == nil {
|
||
authorWithFollowStatus = dto.ConvertUserToResponseWithMutualFollow(post.User, isFollowing, isFollowingMe)
|
||
} else {
|
||
authorWithFollowStatus = dto.ConvertUserToResponse(post.User)
|
||
}
|
||
} else {
|
||
authorWithFollowStatus = dto.ConvertUserToResponse(post.User)
|
||
}
|
||
}
|
||
|
||
// 构建响应
|
||
responseData := &dto.PostResponse{
|
||
ID: post.ID,
|
||
UserID: post.UserID,
|
||
ChannelID: post.ChannelID,
|
||
Title: post.Title,
|
||
Content: post.Content,
|
||
Segments: dto.SegmentsOrDefault(post.Segments, post.Content),
|
||
Images: dto.ConvertPostImagesToResponse(post.Images),
|
||
Status: string(post.Status),
|
||
LikesCount: post.LikesCount,
|
||
CommentsCount: post.CommentsCount,
|
||
FavoritesCount: post.FavoritesCount,
|
||
SharesCount: post.SharesCount,
|
||
ViewsCount: post.ViewsCount,
|
||
IsPinned: post.IsPinned,
|
||
IsLocked: post.IsLocked,
|
||
IsVote: post.IsVote,
|
||
CreatedAt: dto.FormatTime(post.CreatedAt),
|
||
UpdatedAt: dto.FormatTime(post.UpdatedAt),
|
||
ContentEditedAt: dto.FormatTimePointer(post.ContentEditedAt),
|
||
Author: authorWithFollowStatus,
|
||
IsLiked: isLiked,
|
||
IsFavorited: isFavorited,
|
||
}
|
||
|
||
chMap := h.channelMapForPosts([]*model.Post{post})
|
||
responseData.Channel = dto.PostChannelBriefForPost(post, chMap)
|
||
|
||
response.Success(c, responseData)
|
||
}
|
||
|
||
// RecordView 记录帖子浏览(增加浏览量)
|
||
func (h *PostHandler) RecordView(c *gin.Context) {
|
||
id := c.Param("id")
|
||
userID := c.GetString("user_id")
|
||
|
||
// 验证帖子存在
|
||
_, err := h.postService.GetByID(c.Request.Context(), id)
|
||
if err != nil {
|
||
response.NotFound(c, "post not found")
|
||
return
|
||
}
|
||
|
||
// 增加浏览量
|
||
if err := h.postService.IncrementViews(c.Request.Context(), id, userID); err != nil {
|
||
fmt.Printf("[ERROR] Failed to increment views for post %s: %v\n", id, err)
|
||
response.InternalServerError(c, "failed to record view")
|
||
return
|
||
}
|
||
|
||
response.Success(c, gin.H{"success": true})
|
||
}
|
||
|
||
// RecordShare 记录帖子分享(shares_count +1,仅已发布)
|
||
// 支持可选 JSON:{"channel":"wechat"|"qq"|"copy_link"|"system"|...},用于后续统计
|
||
func (h *PostHandler) RecordShare(c *gin.Context) {
|
||
id := c.Param("id")
|
||
_ = c.GetString("user_id") // 预留:登录用户维度统计
|
||
|
||
type shareBody struct {
|
||
Channel string `json:"channel"`
|
||
}
|
||
var body shareBody
|
||
if c.Request.ContentLength > 0 {
|
||
if err := c.ShouldBindJSON(&body); err != nil {
|
||
response.BadRequest(c, "invalid json body")
|
||
return
|
||
}
|
||
}
|
||
|
||
sharesCount, err := h.postService.RecordShare(c.Request.Context(), id, c.GetString("user_id"))
|
||
if err != nil {
|
||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||
response.NotFound(c, "post not found")
|
||
return
|
||
}
|
||
fmt.Printf("[ERROR] RecordShare post %s: %v\n", id, err)
|
||
response.InternalServerError(c, "failed to record share")
|
||
return
|
||
}
|
||
|
||
data := gin.H{"success": true, "shares_count": sharesCount}
|
||
if body.Channel != "" {
|
||
data["channel"] = body.Channel
|
||
}
|
||
response.Success(c, data)
|
||
}
|
||
|
||
// List 获取帖子列表(支持游标分页和偏移分页)
|
||
func (h *PostHandler) List(c *gin.Context) {
|
||
// 检查是否使用游标分页
|
||
// 当 cursor 参数存在时(包括空字符串),使用游标分页
|
||
_, cursorExists := c.GetQuery("cursor")
|
||
if cursorExists {
|
||
h.ListByCursor(c)
|
||
return
|
||
}
|
||
|
||
// 偏移分页(向后兼容)
|
||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||
page, pageSize = normalizePagination(page, pageSize)
|
||
userID := c.Query("user_id")
|
||
tab := c.Query("tab")
|
||
var channelID *string
|
||
if cid := c.Query("channel_id"); cid != "" {
|
||
channelID = &cid
|
||
}
|
||
|
||
// 获取当前用户ID
|
||
currentUserID := c.GetString("user_id")
|
||
|
||
var posts []*model.Post
|
||
var total int64
|
||
var err error
|
||
|
||
// 根据 tab 参数选择不同的获取方式
|
||
switch tab {
|
||
case "follow":
|
||
// 获取关注用户的帖子,需要登录
|
||
if currentUserID == "" {
|
||
response.Unauthorized(c, "请先登录")
|
||
return
|
||
}
|
||
posts, total, err = h.postService.GetFollowingPosts(c.Request.Context(), currentUserID, page, pageSize)
|
||
case "hot":
|
||
// 获取热门帖子
|
||
posts, total, err = h.postService.GetHotPosts(c.Request.Context(), page, pageSize)
|
||
case "latest":
|
||
// 最新帖子
|
||
if userID != "" && userID == currentUserID {
|
||
posts, total, err = h.postService.GetLatestPostsForOwner(c.Request.Context(), page, pageSize, userID, channelID)
|
||
} else {
|
||
posts, total, err = h.postService.GetLatestPosts(c.Request.Context(), page, pageSize, userID, channelID)
|
||
}
|
||
default:
|
||
// 默认获取最新帖子
|
||
if userID != "" && userID == currentUserID {
|
||
posts, total, err = h.postService.GetLatestPostsForOwner(c.Request.Context(), page, pageSize, userID, channelID)
|
||
} else {
|
||
posts, total, err = h.postService.GetLatestPosts(c.Request.Context(), page, pageSize, userID, channelID)
|
||
}
|
||
}
|
||
|
||
if err != nil {
|
||
response.InternalServerError(c, "failed to get posts")
|
||
return
|
||
}
|
||
|
||
posts = h.filterBlockedPosts(c.Request.Context(), posts, currentUserID)
|
||
|
||
// 批量获取交互状态
|
||
postIDs := make([]string, len(posts))
|
||
for i, post := range posts {
|
||
postIDs[i] = post.ID
|
||
}
|
||
isLikedMap, isFavoritedMap, _ := h.postService.GetPostInteractionStatus(c.Request.Context(), postIDs, currentUserID)
|
||
|
||
chMap := h.channelMapForPosts(posts)
|
||
postResponses := dto.BuildPostsWithInteractionResponse(posts, chMap, isLikedMap, isFavoritedMap)
|
||
|
||
response.Paginated(c, postResponses, total, page, pageSize)
|
||
}
|
||
|
||
// ListByCursor 游标分页获取帖子列表
|
||
func (h *PostHandler) ListByCursor(c *gin.Context) {
|
||
userID := c.Query("user_id")
|
||
currentUserID := c.GetString("user_id")
|
||
tab := c.Query("tab")
|
||
var channelID *string
|
||
if cid := c.Query("channel_id"); cid != "" {
|
||
channelID = &cid
|
||
}
|
||
|
||
// 解析游标分页请求
|
||
req := h.parseCursorRequest(c)
|
||
|
||
// 判断是否包含待审核帖子
|
||
includePending := userID != "" && userID == currentUserID
|
||
|
||
var result *cursor.CursorPageResult[*model.Post]
|
||
var err error
|
||
|
||
// 根据 tab 参数选择不同的获取方式
|
||
switch tab {
|
||
case "follow":
|
||
// 获取关注用户的帖子,需要登录
|
||
if currentUserID == "" {
|
||
response.Unauthorized(c, "请先登录")
|
||
return
|
||
}
|
||
result, err = h.postService.GetFollowingPostsByCursor(c.Request.Context(), currentUserID, req)
|
||
case "hot":
|
||
// 获取热门帖子
|
||
result, err = h.postService.GetHotPostsByCursor(c.Request.Context(), req)
|
||
case "latest":
|
||
// 最新帖子
|
||
result, err = h.postService.ListByCursor(c.Request.Context(), userID, includePending, channelID, req)
|
||
default:
|
||
// 默认获取最新帖子
|
||
result, err = h.postService.ListByCursor(c.Request.Context(), userID, includePending, channelID, req)
|
||
}
|
||
|
||
if err != nil {
|
||
response.InternalServerError(c, "failed to get posts")
|
||
return
|
||
}
|
||
|
||
result.Items = h.filterBlockedPosts(c.Request.Context(), result.Items, currentUserID)
|
||
|
||
// 批量获取交互状态
|
||
postIDs := make([]string, len(result.Items))
|
||
for i, post := range result.Items {
|
||
postIDs[i] = post.ID
|
||
}
|
||
isLikedMap, isFavoritedMap, _ := h.postService.GetPostInteractionStatus(c.Request.Context(), postIDs, currentUserID)
|
||
|
||
chMap := h.channelMapForPosts(result.Items)
|
||
postResponses := dto.BuildPostsWithInteractionResponse(result.Items, chMap, isLikedMap, isFavoritedMap)
|
||
|
||
// 构建游标分页响应
|
||
cursorResp := &dto.PostCursorPageResponse{
|
||
Items: postResponses,
|
||
NextCursor: result.NextCursor,
|
||
PrevCursor: result.PrevCursor,
|
||
HasMore: result.HasMore,
|
||
}
|
||
|
||
response.Success(c, cursorResp)
|
||
}
|
||
|
||
// Update 更新帖子
|
||
func (h *PostHandler) Update(c *gin.Context) {
|
||
userID := c.GetString("user_id")
|
||
if userID == "" {
|
||
response.Unauthorized(c, "")
|
||
return
|
||
}
|
||
|
||
id := c.Param("id")
|
||
|
||
post, err := h.postService.GetByID(c.Request.Context(), id)
|
||
if err != nil {
|
||
response.NotFound(c, "post not found")
|
||
return
|
||
}
|
||
|
||
if post.UserID != userID {
|
||
response.Forbidden(c, "cannot update others' post")
|
||
return
|
||
}
|
||
|
||
type UpdateRequest struct {
|
||
Title string `json:"title"`
|
||
Content string `json:"content"`
|
||
Segments model.MessageSegments `json:"segments"`
|
||
Images *[]string `json:"images"`
|
||
}
|
||
|
||
var req UpdateRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
response.BadRequest(c, err.Error())
|
||
return
|
||
}
|
||
|
||
if req.Title != "" {
|
||
post.Title = req.Title
|
||
}
|
||
if req.Content != "" {
|
||
post.Content = req.Content
|
||
}
|
||
if len(req.Segments) > 0 {
|
||
post.Segments = req.Segments
|
||
if req.Content == "" {
|
||
post.Content = dto.ExtractTextContent(req.Segments)
|
||
}
|
||
}
|
||
|
||
err = h.postService.UpdateWithImages(c.Request.Context(), post, req.Images)
|
||
if err != nil {
|
||
response.InternalServerError(c, "failed to update post")
|
||
return
|
||
}
|
||
|
||
post, err = h.postService.GetByID(c.Request.Context(), post.ID)
|
||
if err != nil {
|
||
response.InternalServerError(c, "failed to get updated post")
|
||
return
|
||
}
|
||
|
||
// 获取交互状态
|
||
currentUserID := c.GetString("user_id")
|
||
isLiked, isFavorited := h.postService.GetPostInteractionStatusSingle(c.Request.Context(), post.ID, currentUserID)
|
||
|
||
chMap := h.channelMapForPosts([]*model.Post{post})
|
||
response.Success(c, dto.BuildPostResponse(post, chMap, isLiked, isFavorited))
|
||
}
|
||
|
||
// Delete 删除帖子
|
||
func (h *PostHandler) Delete(c *gin.Context) {
|
||
userID := c.GetString("user_id")
|
||
if userID == "" {
|
||
response.Unauthorized(c, "")
|
||
return
|
||
}
|
||
|
||
id := c.Param("id")
|
||
|
||
post, err := h.postService.GetByID(c.Request.Context(), id)
|
||
if err != nil {
|
||
response.NotFound(c, "post not found")
|
||
return
|
||
}
|
||
|
||
if post.UserID != userID {
|
||
response.Forbidden(c, "cannot delete others' post")
|
||
return
|
||
}
|
||
|
||
err = h.postService.Delete(c.Request.Context(), id)
|
||
if err != nil {
|
||
response.InternalServerError(c, "failed to delete post")
|
||
return
|
||
}
|
||
|
||
response.SuccessWithMessage(c, "post deleted", nil)
|
||
}
|
||
|
||
// Like 点赞帖子
|
||
func (h *PostHandler) Like(c *gin.Context) {
|
||
userID := c.GetString("user_id")
|
||
if userID == "" {
|
||
response.Unauthorized(c, "")
|
||
return
|
||
}
|
||
|
||
id := c.Param("id")
|
||
|
||
err := h.postService.Like(c.Request.Context(), id, userID)
|
||
if err != nil {
|
||
response.InternalServerError(c, "failed to like post")
|
||
return
|
||
}
|
||
|
||
// 获取更新后的帖子状态
|
||
post, err := h.postService.GetByID(c.Request.Context(), id)
|
||
if err != nil {
|
||
response.InternalServerError(c, "failed to get post")
|
||
return
|
||
}
|
||
|
||
// 获取交互状态
|
||
isLiked, isFavorited := h.postService.GetPostInteractionStatusSingle(c.Request.Context(), id, userID)
|
||
|
||
chMap := h.channelMapForPosts([]*model.Post{post})
|
||
response.Success(c, dto.BuildPostResponse(post, chMap, isLiked, isFavorited))
|
||
}
|
||
|
||
// Unlike 取消点赞
|
||
func (h *PostHandler) Unlike(c *gin.Context) {
|
||
userID := c.GetString("user_id")
|
||
if userID == "" {
|
||
response.Unauthorized(c, "")
|
||
return
|
||
}
|
||
|
||
id := c.Param("id")
|
||
|
||
err := h.postService.Unlike(c.Request.Context(), id, userID)
|
||
if err != nil {
|
||
response.InternalServerError(c, "failed to unlike post")
|
||
return
|
||
}
|
||
|
||
// 获取更新后的帖子状态
|
||
post, err := h.postService.GetByID(c.Request.Context(), id)
|
||
if err != nil {
|
||
response.InternalServerError(c, "failed to get post")
|
||
return
|
||
}
|
||
|
||
// 获取交互状态
|
||
isLiked, isFavorited := h.postService.GetPostInteractionStatusSingle(c.Request.Context(), id, userID)
|
||
|
||
chMap := h.channelMapForPosts([]*model.Post{post})
|
||
response.Success(c, dto.BuildPostResponse(post, chMap, isLiked, isFavorited))
|
||
}
|
||
|
||
// Favorite 收藏帖子
|
||
func (h *PostHandler) Favorite(c *gin.Context) {
|
||
userID := c.GetString("user_id")
|
||
if userID == "" {
|
||
response.Unauthorized(c, "")
|
||
return
|
||
}
|
||
|
||
id := c.Param("id")
|
||
|
||
err := h.postService.Favorite(c.Request.Context(), id, userID)
|
||
if err != nil {
|
||
response.InternalServerError(c, "failed to favorite post")
|
||
return
|
||
}
|
||
|
||
// 获取更新后的帖子状态
|
||
post, err := h.postService.GetByID(c.Request.Context(), id)
|
||
if err != nil {
|
||
response.InternalServerError(c, "failed to get post")
|
||
return
|
||
}
|
||
|
||
// 获取交互状态
|
||
isLiked, isFavorited := h.postService.GetPostInteractionStatusSingle(c.Request.Context(), id, userID)
|
||
|
||
chMap := h.channelMapForPosts([]*model.Post{post})
|
||
response.Success(c, dto.BuildPostResponse(post, chMap, isLiked, isFavorited))
|
||
}
|
||
|
||
// Unfavorite 取消收藏
|
||
func (h *PostHandler) Unfavorite(c *gin.Context) {
|
||
userID := c.GetString("user_id")
|
||
if userID == "" {
|
||
response.Unauthorized(c, "")
|
||
return
|
||
}
|
||
|
||
id := c.Param("id")
|
||
|
||
err := h.postService.Unfavorite(c.Request.Context(), id, userID)
|
||
if err != nil {
|
||
response.InternalServerError(c, "failed to unfavorite post")
|
||
return
|
||
}
|
||
|
||
// 获取更新后的帖子状态
|
||
post, err := h.postService.GetByID(c.Request.Context(), id)
|
||
if err != nil {
|
||
response.InternalServerError(c, "failed to get post")
|
||
return
|
||
}
|
||
|
||
// 获取交互状态
|
||
isLiked, isFavorited := h.postService.GetPostInteractionStatusSingle(c.Request.Context(), id, userID)
|
||
|
||
chMap := h.channelMapForPosts([]*model.Post{post})
|
||
response.Success(c, dto.BuildPostResponse(post, chMap, isLiked, isFavorited))
|
||
}
|
||
|
||
// GetUserPosts 获取用户帖子列表(支持游标分页和偏移分页)
|
||
func (h *PostHandler) GetUserPosts(c *gin.Context) {
|
||
userID := c.Param("id")
|
||
currentUserID := c.GetString("user_id")
|
||
|
||
canView, err := h.userService.CanViewPosts(c.Request.Context(), currentUserID, userID)
|
||
if err != nil {
|
||
response.InternalServerError(c, "failed to check privacy settings")
|
||
return
|
||
}
|
||
if !canView {
|
||
response.Forbidden(c, "该用户的帖子列表不对外公开")
|
||
return
|
||
}
|
||
|
||
// 检查是否拉黑了对方
|
||
if currentUserID != "" && currentUserID != userID {
|
||
isBlocked, _ := h.userService.IsBlocked(c.Request.Context(), currentUserID, userID)
|
||
if isBlocked {
|
||
// 检查是否使用游标分页
|
||
cursorStr := c.Query("cursor")
|
||
if cursorStr != "" {
|
||
response.Success(c, &dto.PostCursorPageResponse{
|
||
Items: []*dto.PostResponse{},
|
||
HasMore: false,
|
||
IsBlocked: true,
|
||
})
|
||
} else {
|
||
response.Success(c, gin.H{
|
||
"list": []*dto.PostResponse{},
|
||
"total": 0,
|
||
"page": 1,
|
||
"page_size": 20,
|
||
"total_pages": 0,
|
||
"is_blocked": true,
|
||
})
|
||
}
|
||
return
|
||
}
|
||
}
|
||
|
||
// 检查是否使用游标分页
|
||
cursorStr := c.Query("cursor")
|
||
if cursorStr != "" {
|
||
h.GetUserPostsByCursor(c)
|
||
return
|
||
}
|
||
|
||
// 偏移分页(向后兼容)
|
||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||
page, pageSize = normalizePagination(page, pageSize)
|
||
|
||
includePending := currentUserID != "" && currentUserID == userID
|
||
posts, total, err := h.postService.GetUserPosts(c.Request.Context(), userID, page, pageSize, includePending)
|
||
if err != nil {
|
||
response.InternalServerError(c, "failed to get user posts")
|
||
return
|
||
}
|
||
|
||
// 批量获取交互状态
|
||
postIDs := make([]string, len(posts))
|
||
for i, post := range posts {
|
||
postIDs[i] = post.ID
|
||
}
|
||
isLikedMap, isFavoritedMap, _ := h.postService.GetPostInteractionStatus(c.Request.Context(), postIDs, currentUserID)
|
||
|
||
chMap := h.channelMapForPosts(posts)
|
||
postResponses := dto.BuildPostsWithInteractionResponse(posts, chMap, isLikedMap, isFavoritedMap)
|
||
|
||
response.Paginated(c, postResponses, total, page, pageSize)
|
||
}
|
||
|
||
// GetUserPostsByCursor 游标分页获取用户帖子列表
|
||
func (h *PostHandler) GetUserPostsByCursor(c *gin.Context) {
|
||
userID := c.Param("id")
|
||
currentUserID := c.GetString("user_id")
|
||
|
||
// 解析游标分页请求
|
||
req := h.parseCursorRequest(c)
|
||
|
||
// 判断是否包含待审核帖子
|
||
includePending := currentUserID != "" && currentUserID == userID
|
||
|
||
// 调用游标分页服务
|
||
result, err := h.postService.GetUserPostsByCursor(c.Request.Context(), userID, includePending, req)
|
||
if err != nil {
|
||
response.InternalServerError(c, "failed to get user posts")
|
||
return
|
||
}
|
||
|
||
// 批量获取交互状态
|
||
postIDs := make([]string, len(result.Items))
|
||
for i, post := range result.Items {
|
||
postIDs[i] = post.ID
|
||
}
|
||
isLikedMap, isFavoritedMap, _ := h.postService.GetPostInteractionStatus(c.Request.Context(), postIDs, currentUserID)
|
||
|
||
chMap := h.channelMapForPosts(result.Items)
|
||
postResponses := dto.BuildPostsWithInteractionResponse(result.Items, chMap, isLikedMap, isFavoritedMap)
|
||
|
||
// 构建游标分页响应
|
||
cursorResp := &dto.PostCursorPageResponse{
|
||
Items: postResponses,
|
||
NextCursor: result.NextCursor,
|
||
PrevCursor: result.PrevCursor,
|
||
HasMore: result.HasMore,
|
||
}
|
||
|
||
response.Success(c, cursorResp)
|
||
}
|
||
|
||
// GetFavorites 获取收藏列表
|
||
func (h *PostHandler) GetFavorites(c *gin.Context) {
|
||
userID := c.Param("id")
|
||
currentUserID := c.GetString("user_id")
|
||
|
||
canView, err := h.userService.CanViewFavorites(c.Request.Context(), currentUserID, userID)
|
||
if err != nil {
|
||
response.InternalServerError(c, "failed to check privacy settings")
|
||
return
|
||
}
|
||
if !canView {
|
||
response.Forbidden(c, "该用户的收藏列表不对外公开")
|
||
return
|
||
}
|
||
|
||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||
page, pageSize = normalizePagination(page, pageSize)
|
||
|
||
posts, total, err := h.postService.GetFavorites(c.Request.Context(), userID, page, pageSize)
|
||
if err != nil {
|
||
response.InternalServerError(c, "failed to get favorites")
|
||
return
|
||
}
|
||
|
||
postIDs := make([]string, len(posts))
|
||
for i, post := range posts {
|
||
postIDs[i] = post.ID
|
||
}
|
||
isLikedMap, isFavoritedMap, _ := h.postService.GetPostInteractionStatus(c.Request.Context(), postIDs, currentUserID)
|
||
|
||
chMap := h.channelMapForPosts(posts)
|
||
postResponses := dto.BuildPostsWithInteractionResponse(posts, chMap, isLikedMap, isFavoritedMap)
|
||
|
||
response.Paginated(c, postResponses, total, page, pageSize)
|
||
}
|
||
|
||
// Search 搜索帖子
|
||
func (h *PostHandler) Search(c *gin.Context) {
|
||
keyword := c.Query("keyword")
|
||
|
||
// 检查是否使用游标分页
|
||
cursorStr := c.Query("cursor")
|
||
if cursorStr != "" {
|
||
h.SearchByCursor(c)
|
||
return
|
||
}
|
||
|
||
// 偏移分页(向后兼容)
|
||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||
page, pageSize = normalizePagination(page, pageSize)
|
||
|
||
posts, total, err := h.postService.Search(c.Request.Context(), keyword, page, pageSize)
|
||
if err != nil {
|
||
response.InternalServerError(c, "failed to search posts")
|
||
return
|
||
}
|
||
|
||
// 批量获取交互状态
|
||
currentUserID := c.GetString("user_id")
|
||
|
||
posts = h.filterBlockedPosts(c.Request.Context(), posts, currentUserID)
|
||
|
||
postIDs := make([]string, len(posts))
|
||
for i, post := range posts {
|
||
postIDs[i] = post.ID
|
||
}
|
||
isLikedMap, isFavoritedMap, _ := h.postService.GetPostInteractionStatus(c.Request.Context(), postIDs, currentUserID)
|
||
|
||
chMap := h.channelMapForPosts(posts)
|
||
postResponses := dto.BuildPostsWithInteractionResponse(posts, chMap, isLikedMap, isFavoritedMap)
|
||
|
||
response.Paginated(c, postResponses, total, page, pageSize)
|
||
}
|
||
|
||
// SearchByCursor 游标分页搜索帖子
|
||
func (h *PostHandler) SearchByCursor(c *gin.Context) {
|
||
keyword := c.Query("keyword")
|
||
currentUserID := c.GetString("user_id")
|
||
|
||
// 解析游标分页请求
|
||
req := h.parseCursorRequest(c)
|
||
|
||
// 调用游标分页服务
|
||
result, err := h.postService.SearchByCursor(c.Request.Context(), keyword, req)
|
||
if err != nil {
|
||
response.InternalServerError(c, "failed to search posts")
|
||
return
|
||
}
|
||
|
||
result.Items = h.filterBlockedPosts(c.Request.Context(), result.Items, currentUserID)
|
||
|
||
// 批量获取交互状态
|
||
postIDs := make([]string, len(result.Items))
|
||
for i, post := range result.Items {
|
||
postIDs[i] = post.ID
|
||
}
|
||
isLikedMap, isFavoritedMap, _ := h.postService.GetPostInteractionStatus(c.Request.Context(), postIDs, currentUserID)
|
||
|
||
chMap := h.channelMapForPosts(result.Items)
|
||
postResponses := dto.BuildPostsWithInteractionResponse(result.Items, chMap, isLikedMap, isFavoritedMap)
|
||
|
||
// 构建游标分页响应
|
||
cursorResp := &dto.PostCursorPageResponse{
|
||
Items: postResponses,
|
||
NextCursor: result.NextCursor,
|
||
PrevCursor: result.PrevCursor,
|
||
HasMore: result.HasMore,
|
||
}
|
||
|
||
response.Success(c, cursorResp)
|
||
}
|
||
|
||
// parseCursorRequest 解析游标分页请求参数
|
||
func (h *PostHandler) parseCursorRequest(c *gin.Context) *cursor.PageRequest {
|
||
cursorStr := c.Query("cursor")
|
||
direction := cursor.Direction(c.DefaultQuery("direction", "forward"))
|
||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||
|
||
return cursor.NewPageRequest(cursorStr, direction, pageSize)
|
||
}
|