- Removed Gorse-related configurations, handlers, and dependencies from the codebase. - Introduced HotRank feature with configuration options for ranking posts based on recent activity. - Updated application structure to support HotRank processing, including new caching mechanisms and database interactions. - Cleaned up related DTOs and repository methods to reflect the removal of Gorse and the addition of HotRank functionality.
666 lines
19 KiB
Go
666 lines
19 KiB
Go
package handler
|
||
|
||
import (
|
||
"errors"
|
||
"fmt"
|
||
"strconv"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
|
||
"carrot_bbs/internal/dto"
|
||
"carrot_bbs/internal/model"
|
||
"carrot_bbs/internal/pkg/cursor"
|
||
"carrot_bbs/internal/pkg/response"
|
||
"carrot_bbs/internal/service"
|
||
)
|
||
|
||
// PostHandler 帖子处理器
|
||
type PostHandler struct {
|
||
postService service.PostService
|
||
userService service.UserService
|
||
}
|
||
|
||
// NewPostHandler 创建帖子处理器
|
||
func NewPostHandler(postService service.PostService, userService service.UserService) *PostHandler {
|
||
return &PostHandler{
|
||
postService: postService,
|
||
userService: userService,
|
||
}
|
||
}
|
||
|
||
// Create 创建帖子
|
||
func (h *PostHandler) Create(c *gin.Context) {
|
||
userID := c.GetString("user_id")
|
||
if userID == "" {
|
||
response.Unauthorized(c, "")
|
||
return
|
||
}
|
||
|
||
type CreateRequest struct {
|
||
Title string `json:"title" binding:"required"`
|
||
Content string `json:"content" binding:"required"`
|
||
Images []string `json:"images"`
|
||
}
|
||
|
||
var req CreateRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
response.BadRequest(c, err.Error())
|
||
return
|
||
}
|
||
|
||
post, err := h.postService.Create(c.Request.Context(), userID, req.Title, req.Content, req.Images)
|
||
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
|
||
}
|
||
|
||
response.Success(c, dto.ConvertPostToResponse(post, 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 currentUserID != "" && post.User != nil {
|
||
_, isFollowing, isFollowingMe, err := h.userService.GetUserByIDWithMutualFollowStatus(c.Request.Context(), post.UserID, currentUserID)
|
||
if err == nil {
|
||
authorWithFollowStatus = dto.ConvertUserToResponseWithMutualFollow(post.User, isFollowing, isFollowingMe)
|
||
} else {
|
||
// 如果出错,使用默认的author
|
||
authorWithFollowStatus = dto.ConvertUserToResponse(post.User)
|
||
}
|
||
}
|
||
|
||
// 构建响应
|
||
responseData := &dto.PostResponse{
|
||
ID: post.ID,
|
||
UserID: post.UserID,
|
||
Title: post.Title,
|
||
Content: 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,
|
||
}
|
||
|
||
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})
|
||
}
|
||
|
||
// 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")
|
||
|
||
// 获取当前用户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)
|
||
} else {
|
||
posts, total, err = h.postService.GetLatestPosts(c.Request.Context(), page, pageSize, userID)
|
||
}
|
||
default:
|
||
// 默认获取最新帖子
|
||
if userID != "" && userID == currentUserID {
|
||
posts, total, err = h.postService.GetLatestPostsForOwner(c.Request.Context(), page, pageSize, userID)
|
||
} else {
|
||
posts, total, err = h.postService.GetLatestPosts(c.Request.Context(), page, pageSize, userID)
|
||
}
|
||
}
|
||
|
||
if err != nil {
|
||
response.InternalServerError(c, "failed to get 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)
|
||
|
||
// 转换为响应结构
|
||
postResponses := dto.BuildPostsWithInteractionResponse(posts, 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")
|
||
|
||
// 解析游标分页请求
|
||
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, req)
|
||
default:
|
||
// 默认获取最新帖子
|
||
result, err = h.postService.ListByCursor(c.Request.Context(), userID, includePending, req)
|
||
}
|
||
|
||
if err != nil {
|
||
response.InternalServerError(c, "failed to get 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)
|
||
|
||
// 转换为响应结构
|
||
postResponses := dto.BuildPostsWithInteractionResponse(result.Items, 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"`
|
||
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
|
||
}
|
||
|
||
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)
|
||
|
||
response.Success(c, dto.BuildPostResponse(post, 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)
|
||
|
||
response.Success(c, dto.BuildPostResponse(post, 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)
|
||
|
||
response.Success(c, dto.BuildPostResponse(post, 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)
|
||
|
||
response.Success(c, dto.BuildPostResponse(post, 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)
|
||
|
||
response.Success(c, dto.BuildPostResponse(post, isLiked, isFavorited))
|
||
}
|
||
|
||
// GetUserPosts 获取用户帖子列表(支持游标分页和偏移分页)
|
||
func (h *PostHandler) GetUserPosts(c *gin.Context) {
|
||
// 检查是否使用游标分页
|
||
cursorStr := c.Query("cursor")
|
||
if cursorStr != "" {
|
||
h.GetUserPostsByCursor(c)
|
||
return
|
||
}
|
||
|
||
// 偏移分页(向后兼容)
|
||
userID := c.Param("id")
|
||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||
page, pageSize = normalizePagination(page, pageSize)
|
||
|
||
currentUserID := c.GetString("user_id")
|
||
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)
|
||
|
||
// 转换为响应结构
|
||
postResponses := dto.BuildPostsWithInteractionResponse(posts, 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)
|
||
|
||
// 转换为响应结构
|
||
postResponses := dto.BuildPostsWithInteractionResponse(result.Items, 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")
|
||
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
|
||
}
|
||
|
||
// 批量获取交互状态
|
||
currentUserID := c.GetString("user_id")
|
||
postIDs := make([]string, len(posts))
|
||
for i, post := range posts {
|
||
postIDs[i] = post.ID
|
||
}
|
||
isLikedMap, isFavoritedMap, _ := h.postService.GetPostInteractionStatus(c.Request.Context(), postIDs, currentUserID)
|
||
|
||
// 转换为响应结构
|
||
postResponses := dto.BuildPostsWithInteractionResponse(posts, 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")
|
||
postIDs := make([]string, len(posts))
|
||
for i, post := range posts {
|
||
postIDs[i] = post.ID
|
||
}
|
||
isLikedMap, isFavoritedMap, _ := h.postService.GetPostInteractionStatus(c.Request.Context(), postIDs, currentUserID)
|
||
|
||
// 转换为响应结构
|
||
postResponses := dto.BuildPostsWithInteractionResponse(posts, 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
|
||
}
|
||
|
||
// 批量获取交互状态
|
||
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)
|
||
|
||
// 转换为响应结构
|
||
postResponses := dto.BuildPostsWithInteractionResponse(result.Items, 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)
|
||
}
|