feat(privacy): add user privacy settings and account deletion system
Add privacy settings allowing users to control visibility of their followers, following, posts, and favorites lists with three visibility levels: everyone, following, and self. Also implement account deletion workflow with 30-day cooldown period. - Add PrivacySettings model with visibility levels - Add DTOs for privacy settings and account deletion - Add repository methods for privacy settings and deletion - Add service methods to check visibility permissions - Add handlers for get/update privacy settings and account deletion - Add new routes for privacy settings and account management - Add account cleanup service for hard-deleting expired accounts - Modify login to cancel deletion when user logs in BREAKING CHANGE: Users with pending_deletion status can no longer log in; logging in now automatically cancels deletion requests.
This commit is contained in:
@@ -17,9 +17,9 @@ import (
|
||||
|
||||
// PostHandler 帖子处理器
|
||||
type PostHandler struct {
|
||||
postService service.PostService
|
||||
userService service.UserService
|
||||
channelService service.ChannelService
|
||||
postService service.PostService
|
||||
userService service.UserService
|
||||
channelService service.ChannelService
|
||||
}
|
||||
|
||||
// NewPostHandler 创建帖子处理器
|
||||
@@ -116,27 +116,27 @@ func (h *PostHandler) GetByID(c *gin.Context) {
|
||||
|
||||
// 构建响应
|
||||
responseData := &dto.PostResponse{
|
||||
ID: post.ID,
|
||||
UserID: post.UserID,
|
||||
ChannelID: post.ChannelID,
|
||||
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,
|
||||
ID: post.ID,
|
||||
UserID: post.UserID,
|
||||
ChannelID: post.ChannelID,
|
||||
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,
|
||||
IsLiked: isLiked,
|
||||
IsFavorited: isFavorited,
|
||||
}
|
||||
|
||||
chMap := h.channelMapForPosts([]*model.Post{post})
|
||||
@@ -553,6 +553,19 @@ func (h *PostHandler) Unfavorite(c *gin.Context) {
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 检查是否使用游标分页
|
||||
cursorStr := c.Query("cursor")
|
||||
if cursorStr != "" {
|
||||
@@ -561,12 +574,10 @@ func (h *PostHandler) GetUserPosts(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)
|
||||
|
||||
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 {
|
||||
@@ -629,6 +640,18 @@ func (h *PostHandler) GetUserPostsByCursor(c *gin.Context) {
|
||||
// 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)
|
||||
@@ -639,8 +662,6 @@ func (h *PostHandler) GetFavorites(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 批量获取交互状态
|
||||
currentUserID := c.GetString("user_id")
|
||||
postIDs := make([]string, len(posts))
|
||||
for i, post := range posts {
|
||||
postIDs[i] = post.ID
|
||||
|
||||
Reference in New Issue
Block a user