Files
backend/internal/handler/user_handler.go
lafay 90495385cd
All checks were successful
Build Backend / build (push) Successful in 4m24s
Build Backend / build-docker (push) Successful in 1m14s
feat(moderation): add user profile content moderation for avatars, covers, and bios
2026-04-15 11:50:47 +08:00

1007 lines
28 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package handler
import (
"cmp"
"context"
"crypto/sha256"
"fmt"
"strconv"
"github.com/gin-gonic/gin"
"carrot_bbs/internal/dto"
"carrot_bbs/internal/model"
"carrot_bbs/internal/pkg/response"
"carrot_bbs/internal/service"
)
const (
maxPageSize = 100
defaultPageSize = 20
)
func normalizePagination(page, pageSize int) (int, int) {
if page < 1 {
page = 1
}
if pageSize < 1 || pageSize > maxPageSize {
pageSize = defaultPageSize
}
return page, pageSize
}
// UserHandler 用户处理器
type UserHandler struct {
userService service.UserService
activityService service.UserActivityService
profileAuditService service.UserProfileAuditService
jwtService *service.JWTService
logService *service.LogService
}
func NewUserHandler(userService service.UserService, activityService service.UserActivityService, logService *service.LogService) *UserHandler {
return &UserHandler{
userService: userService,
activityService: activityService,
logService: logService,
}
}
func (h *UserHandler) SetProfileAuditService(profileAuditService service.UserProfileAuditService) {
h.profileAuditService = profileAuditService
}
// SetJWTService 设置JWT服务
func (h *UserHandler) SetJWTService(jwtService *service.JWTService) {
h.jwtService = jwtService
}
// SetActivityService 设置活跃统计服务
func (h *UserHandler) SetActivityService(activityService service.UserActivityService) {
h.activityService = activityService
}
// generateTokenID 生成Token ID
func generateTokenID(token string) string {
h := sha256.New()
h.Write([]byte(token))
return fmt.Sprintf("%x", h.Sum(nil))[:16]
}
// Register 用户注册
func (h *UserHandler) Register(c *gin.Context) {
type RegisterRequest struct {
Username string `json:"username" binding:"required"`
Email string `json:"email" binding:"required,email"`
Password string `json:"password" binding:"required,min=6"`
Nickname string `json:"nickname" binding:"required"`
Phone string `json:"phone"`
VerificationCode string `json:"verification_code" binding:"required"`
}
var req RegisterRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
user, err := h.userService.Register(c.Request.Context(), req.Username, req.Email, req.Password, req.Nickname, req.Phone, req.VerificationCode)
if err != nil {
response.HandleError(c, err, "failed to register")
return
}
// 生成Token
accessToken, _ := h.jwtService.GenerateAccessToken(user.ID, user.Username)
refreshToken, _ := h.jwtService.GenerateRefreshToken(user.ID, user.Username)
ip := c.ClientIP()
userAgent := c.GetHeader("User-Agent")
// 记录用户活跃
if h.activityService != nil {
go func() {
ctx := context.Background()
loginType := model.LoginTypeLogin
h.activityService.RecordUserActive(ctx, user.ID, loginType, ip, userAgent)
}()
}
// 记录注册后首次登录
if h.logService != nil {
go func() {
h.logService.LoginLog.RecordLogin(&model.LoginLog{
UserID: user.ID,
UserName: user.Username,
NickName: user.Nickname,
LoginType: string(model.LoginTypePassword),
Event: string(model.LoginEventLogin),
IP: ip,
UserAgent: userAgent,
Result: string(model.LoginResultSuccess),
TokenID: generateTokenID(refreshToken),
})
}()
}
response.Success(c, gin.H{
"user": dto.ConvertUserToSelfResponse(user, 0),
"token": accessToken,
"refresh_token": refreshToken,
})
}
// Login 用户登录
func (h *UserHandler) Login(c *gin.Context) {
type LoginRequest struct {
Username string `json:"username"`
Account string `json:"account"`
Password string `json:"password" binding:"required"`
}
var req LoginRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
account := cmp.Or(req.Account, req.Username)
if account == "" {
response.BadRequest(c, "username or account is required")
return
}
user, err := h.userService.Login(c.Request.Context(), account, req.Password)
if err != nil {
response.HandleError(c, err, "failed to login")
return
}
// 生成Token
accessToken, _ := h.jwtService.GenerateAccessToken(user.ID, user.Username)
refreshToken, _ := h.jwtService.GenerateRefreshToken(user.ID, user.Username)
ip := c.ClientIP()
userAgent := c.GetHeader("User-Agent")
// 记录用户活跃
if h.activityService != nil {
go func() {
ctx := context.Background()
loginType := model.LoginTypeLogin
h.activityService.RecordUserActive(ctx, user.ID, loginType, ip, userAgent)
}()
}
// 更新登录日志补充IP和UserAgent
if h.logService != nil {
ip := c.ClientIP()
userAgent := c.GetHeader("User-Agent")
go func() {
h.logService.LoginLog.RecordLogin(&model.LoginLog{
UserID: user.ID,
UserName: user.Username,
NickName: user.Nickname,
LoginType: string(model.LoginTypePassword),
Event: string(model.LoginEventLogin),
IP: ip,
UserAgent: userAgent,
Result: string(model.LoginResultSuccess),
})
}()
}
response.Success(c, gin.H{
"user": dto.ConvertUserToSelfResponse(user, int(user.PostsCount)),
"token": accessToken,
"refresh_token": refreshToken,
})
}
// SendRegisterCode 发送注册验证码
func (h *UserHandler) SendRegisterCode(c *gin.Context) {
type SendCodeRequest struct {
Email string `json:"email" binding:"required,email"`
}
var req SendCodeRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
clientIP := c.ClientIP()
if err := h.userService.SendRegisterCode(c.Request.Context(), req.Email, clientIP); err != nil {
response.HandleError(c, err, "failed to send register verification code")
return
}
response.Success(c, gin.H{"success": true})
}
func (h *UserHandler) SendPasswordResetCode(c *gin.Context) {
type SendCodeRequest struct {
Email string `json:"email" binding:"required,email"`
}
var req SendCodeRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
clientIP := c.ClientIP()
if err := h.userService.SendPasswordResetCode(c.Request.Context(), req.Email, clientIP); err != nil {
response.HandleError(c, err, "failed to send reset verification code")
return
}
response.Success(c, gin.H{"success": true})
}
// ResetPassword 找回密码并重置
func (h *UserHandler) ResetPassword(c *gin.Context) {
type ResetPasswordRequest struct {
Email string `json:"email" binding:"required,email"`
VerificationCode string `json:"verification_code" binding:"required"`
NewPassword string `json:"new_password" binding:"required,min=6"`
}
var req ResetPasswordRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
if err := h.userService.ResetPasswordByEmail(c.Request.Context(), req.Email, req.VerificationCode, req.NewPassword); err != nil {
response.HandleError(c, err, "failed to reset password")
return
}
response.Success(c, gin.H{"success": true})
}
// GetCurrentUser 获取当前用户
func (h *UserHandler) GetCurrentUser(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
response.Unauthorized(c, "")
return
}
user, err := h.userService.GetUserByID(c.Request.Context(), userID)
if err != nil {
response.NotFound(c, "user not found")
return
}
postsCount, err := h.userService.GetUserPostCount(c.Request.Context(), userID)
if err != nil {
postsCount = int64(user.PostsCount)
}
response.Success(c, dto.ConvertUserToSelfResponse(user, int(postsCount)))
}
// GetUserByID 获取指定用户
func (h *UserHandler) GetUserByID(c *gin.Context) {
id := c.Param("id")
currentUserID := c.GetString("user_id")
// 获取用户信息,包含双向关注状态
user, isFollowing, isFollowingMe, err := h.userService.GetUserByIDWithMutualFollowStatus(c.Request.Context(), id, currentUserID)
if err != nil {
response.NotFound(c, "user not found")
return
}
// 实时计算帖子数量
postsCount, err := h.userService.GetUserPostCount(c.Request.Context(), id)
if err != nil {
// 如果获取失败,使用数据库中的值
postsCount = int64(user.PostsCount)
}
// 转换为响应格式,包含双向关注状态和实时计算的帖子数量
userResponse := dto.ConvertUserToResponseWithMutualFollowAndPostsCount(user, isFollowing, isFollowingMe, int(postsCount))
response.Success(c, userResponse)
}
// UpdateUser 更新用户
func (h *UserHandler) UpdateUser(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
response.Unauthorized(c, "")
return
}
type UpdateRequest struct {
Nickname string `json:"nickname"`
Bio string `json:"bio"`
Website string `json:"website"`
Location string `json:"location"`
Avatar string `json:"avatar"`
CoverURL string `json:"cover_url"`
Phone *string `json:"phone"`
Email *string `json:"email"`
}
var req UpdateRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
user, err := h.userService.GetUserByID(c.Request.Context(), userID)
if err != nil {
response.NotFound(c, "user not found")
return
}
if req.Nickname != "" {
user.Nickname = req.Nickname
}
if req.Website != "" {
user.Website = req.Website
}
if req.Location != "" {
user.Location = req.Location
}
if req.Phone != nil {
user.Phone = req.Phone
}
if req.Email != nil {
if user.Email == nil || *user.Email != *req.Email {
user.EmailVerified = false
}
user.Email = req.Email
}
avatarNeedsAudit := req.Avatar != "" && req.Avatar != user.Avatar && h.profileAuditService != nil
coverNeedsAudit := req.CoverURL != "" && req.CoverURL != user.CoverURL && h.profileAuditService != nil
bioNeedsAudit := req.Bio != "" && req.Bio != user.Bio && h.profileAuditService != nil
if avatarNeedsAudit {
if _, err := h.profileAuditService.SubmitAvatarAudit(c.Request.Context(), userID, req.Avatar); err != nil {
avatarNeedsAudit = false
user.Avatar = req.Avatar
}
} else if req.Avatar != "" {
user.Avatar = req.Avatar
}
if coverNeedsAudit {
if _, err := h.profileAuditService.SubmitCoverAudit(c.Request.Context(), userID, req.CoverURL); err != nil {
coverNeedsAudit = false
user.CoverURL = req.CoverURL
}
} else if req.CoverURL != "" {
user.CoverURL = req.CoverURL
}
if bioNeedsAudit {
if _, err := h.profileAuditService.SubmitBioAudit(c.Request.Context(), userID, req.Bio); err != nil {
bioNeedsAudit = false
user.Bio = req.Bio
}
} else if req.Bio != "" {
user.Bio = req.Bio
}
err = h.userService.UpdateUser(c.Request.Context(), user)
if err != nil {
response.InternalServerError(c, "failed to update user")
return
}
postsCount, err := h.userService.GetUserPostCount(c.Request.Context(), userID)
if err != nil {
postsCount = int64(user.PostsCount)
}
resp := gin.H{"user": dto.ConvertUserToDetailResponseWithPostsCount(user, int(postsCount))}
if avatarNeedsAudit || coverNeedsAudit || bioNeedsAudit {
resp["audit_pending"] = true
}
response.Success(c, resp)
}
// SendEmailVerifyCode 发送当前用户邮箱验证码
func (h *UserHandler) SendEmailVerifyCode(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
response.Unauthorized(c, "")
return
}
type SendCodeRequest struct {
Email string `json:"email" binding:"required,email"`
}
var req SendCodeRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
if err := h.userService.SendCurrentUserEmailVerifyCode(c.Request.Context(), userID, req.Email, c.ClientIP()); err != nil {
response.HandleError(c, err, "failed to send email verify code")
return
}
response.Success(c, gin.H{"success": true})
}
// VerifyEmail 验证当前用户邮箱
func (h *UserHandler) VerifyEmail(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
response.Unauthorized(c, "")
return
}
type VerifyEmailRequest struct {
Email string `json:"email" binding:"required,email"`
VerificationCode string `json:"verification_code" binding:"required"`
}
var req VerifyEmailRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
if err := h.userService.VerifyCurrentUserEmail(c.Request.Context(), userID, req.Email, req.VerificationCode); err != nil {
response.HandleError(c, err, "failed to verify email")
return
}
response.Success(c, gin.H{"success": true})
}
// RefreshToken 刷新Token
func (h *UserHandler) RefreshToken(c *gin.Context) {
type RefreshRequest struct {
RefreshToken string `json:"refresh_token" binding:"required"`
}
var req RefreshRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
// 解析 refresh token
claims, err := h.jwtService.ParseToken(req.RefreshToken)
if err != nil {
response.Unauthorized(c, "invalid refresh token")
return
}
// 获取用户信息
user, err := h.userService.GetUserByID(c.Request.Context(), claims.UserID)
if err != nil {
response.InternalServerError(c, "user not found")
return
}
// 生成新 token
accessToken, _ := h.jwtService.GenerateAccessToken(user.ID, user.Username)
refreshToken, _ := h.jwtService.GenerateRefreshToken(user.ID, user.Username)
ip := c.ClientIP()
userAgent := c.GetHeader("User-Agent")
// 记录用户活跃
if h.activityService != nil {
go func() {
ctx := context.Background()
loginType := model.LoginTypeRefresh
h.activityService.RecordUserActive(ctx, user.ID, loginType, ip, userAgent)
}()
}
// 记录Token刷新日志
if h.logService != nil {
go func() {
h.logService.LoginLog.RecordLogin(&model.LoginLog{
UserID: user.ID,
UserName: user.Username,
NickName: user.Nickname,
LoginType: string(model.LoginTypePassword),
Event: string(model.LoginEventRefresh),
IP: ip,
UserAgent: userAgent,
Result: string(model.LoginResultSuccess),
TokenID: generateTokenID(req.RefreshToken),
})
}()
}
response.Success(c, gin.H{
"token": accessToken,
"refresh_token": refreshToken,
})
}
// FollowUser 关注用户
func (h *UserHandler) FollowUser(c *gin.Context) {
userID := c.Param("id")
currentUserID := c.GetString("user_id")
if userID == currentUserID {
response.BadRequest(c, "cannot follow yourself")
return
}
err := h.userService.FollowUser(c.Request.Context(), currentUserID, userID)
if err != nil {
response.InternalServerError(c, "failed to follow user")
return
}
response.Success(c, gin.H{"success": true})
}
// UnfollowUser 取消关注用户
func (h *UserHandler) UnfollowUser(c *gin.Context) {
userID := c.Param("id")
currentUserID := c.GetString("user_id")
err := h.userService.UnfollowUser(c.Request.Context(), currentUserID, userID)
if err != nil {
response.InternalServerError(c, "failed to unfollow user")
return
}
response.Success(c, gin.H{"success": true})
}
// BlockUser 拉黑用户
func (h *UserHandler) BlockUser(c *gin.Context) {
targetUserID := c.Param("id")
currentUserID := c.GetString("user_id")
if targetUserID == currentUserID {
response.BadRequest(c, "cannot block yourself")
return
}
err := h.userService.BlockUser(c.Request.Context(), currentUserID, targetUserID)
if err != nil {
response.HandleError(c, err, "failed to block user")
return
}
response.Success(c, gin.H{"success": true})
}
// UnblockUser 取消拉黑
func (h *UserHandler) UnblockUser(c *gin.Context) {
targetUserID := c.Param("id")
currentUserID := c.GetString("user_id")
if targetUserID == currentUserID {
response.BadRequest(c, "cannot unblock yourself")
return
}
err := h.userService.UnblockUser(c.Request.Context(), currentUserID, targetUserID)
if err != nil {
response.HandleError(c, err, "failed to unblock user")
return
}
response.Success(c, gin.H{"success": true})
}
// GetBlockedUsers 获取黑名单列表
func (h *UserHandler) GetBlockedUsers(c *gin.Context) {
currentUserID := c.GetString("user_id")
if currentUserID == "" {
response.Unauthorized(c, "")
return
}
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
page, pageSize = normalizePagination(page, pageSize)
users, total, err := h.userService.GetBlockedUsers(c.Request.Context(), currentUserID, page, pageSize)
if err != nil {
response.InternalServerError(c, "failed to get blocked users")
return
}
userIDs := make([]string, len(users))
for i, u := range users {
userIDs[i] = u.ID
}
postsCountMap, _ := h.userService.GetUserPostCountBatch(c.Request.Context(), userIDs)
userResponses := dto.ConvertUsersToResponseWithMutualFollowAndPostsCount(users, nil, postsCountMap)
response.Paginated(c, userResponses, total, page, pageSize)
}
// GetBlockStatus 获取拉黑状态
func (h *UserHandler) GetBlockStatus(c *gin.Context) {
targetUserID := c.Param("id")
currentUserID := c.GetString("user_id")
if currentUserID == "" {
response.Unauthorized(c, "")
return
}
if targetUserID == "" {
response.BadRequest(c, "target user id is required")
return
}
isBlocked, err := h.userService.IsBlocked(c.Request.Context(), currentUserID, targetUserID)
if err != nil {
response.InternalServerError(c, "failed to get block status")
return
}
response.Success(c, gin.H{"is_blocked": isBlocked})
}
// GetFollowingList 获取关注列表
func (h *UserHandler) GetFollowingList(c *gin.Context) {
userID := c.Param("id")
currentUserID := c.GetString("user_id")
canView, err := h.userService.CanViewFollowing(c.Request.Context(), currentUserID, userID)
if err != nil {
response.InternalServerError(c, "failed to check privacy settings")
return
}
if !canView {
response.Forbidden(c, "该用户的关注列表不对外公开")
return
}
page := c.DefaultQuery("page", "1")
pageSize := c.DefaultQuery("page_size", "20")
users, err := h.userService.GetFollowingList(c.Request.Context(), userID, page, pageSize)
if err != nil {
response.InternalServerError(c, "failed to get following list")
return
}
var userResponses []*dto.UserResponse
if len(users) > 0 {
userIDs := make([]string, len(users))
for i, u := range users {
userIDs[i] = u.ID
}
postsCountMap, _ := h.userService.GetUserPostCountBatch(c.Request.Context(), userIDs)
if currentUserID != "" && userID == currentUserID {
statusMap, _ := h.userService.GetMutualFollowStatus(c.Request.Context(), currentUserID, userIDs)
userResponses = dto.ConvertUsersToResponseWithMutualFollowAndPostsCount(users, statusMap, postsCountMap)
} else {
userResponses = dto.ConvertUsersToResponseWithMutualFollowAndPostsCount(users, nil, postsCountMap)
}
} else {
userResponses = dto.ConvertUsersToResponse(users)
}
response.Success(c, gin.H{
"list": userResponses,
})
}
// GetFollowersList 获取粉丝列表
func (h *UserHandler) GetFollowersList(c *gin.Context) {
userID := c.Param("id")
currentUserID := c.GetString("user_id")
canView, err := h.userService.CanViewFollowers(c.Request.Context(), currentUserID, userID)
if err != nil {
response.InternalServerError(c, "failed to check privacy settings")
return
}
if !canView {
response.Forbidden(c, "该用户的粉丝列表不对外公开")
return
}
page := c.DefaultQuery("page", "1")
pageSize := c.DefaultQuery("page_size", "20")
users, err := h.userService.GetFollowersList(c.Request.Context(), userID, page, pageSize)
if err != nil {
response.InternalServerError(c, "failed to get followers list")
return
}
var userResponses []*dto.UserResponse
if len(users) > 0 {
userIDs := make([]string, len(users))
for i, u := range users {
userIDs[i] = u.ID
}
postsCountMap, _ := h.userService.GetUserPostCountBatch(c.Request.Context(), userIDs)
if currentUserID != "" && userID == currentUserID {
statusMap, _ := h.userService.GetMutualFollowStatus(c.Request.Context(), currentUserID, userIDs)
userResponses = dto.ConvertUsersToResponseWithMutualFollowAndPostsCount(users, statusMap, postsCountMap)
} else {
userResponses = dto.ConvertUsersToResponseWithMutualFollowAndPostsCount(users, nil, postsCountMap)
}
} else {
userResponses = dto.ConvertUsersToResponse(users)
}
response.Success(c, gin.H{
"list": userResponses,
})
}
// CheckUsername 检查用户名是否可用
func (h *UserHandler) CheckUsername(c *gin.Context) {
username := c.Query("username")
if username == "" {
response.BadRequest(c, "username is required")
return
}
available, err := h.userService.CheckUsernameAvailable(c.Request.Context(), username)
if err != nil {
response.InternalServerError(c, "failed to check username")
return
}
response.Success(c, gin.H{"available": available})
}
// ChangePassword 修改密码
func (h *UserHandler) ChangePassword(c *gin.Context) {
currentUserID := c.GetString("user_id")
type ChangePasswordRequest struct {
OldPassword string `json:"old_password" binding:"required"`
NewPassword string `json:"new_password" binding:"required,min=6"`
VerificationCode string `json:"verification_code" binding:"required"`
}
var req ChangePasswordRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
err := h.userService.ChangePassword(c.Request.Context(), currentUserID, req.OldPassword, req.NewPassword, req.VerificationCode)
if err != nil {
response.HandleError(c, err, "failed to change password")
return
}
response.Success(c, gin.H{"success": true})
}
// SendChangePasswordCode 发送修改密码验证码
func (h *UserHandler) SendChangePasswordCode(c *gin.Context) {
currentUserID := c.GetString("user_id")
if currentUserID == "" {
response.Unauthorized(c, "")
return
}
err := h.userService.SendChangePasswordCode(c.Request.Context(), currentUserID, c.ClientIP())
if err != nil {
response.HandleError(c, err, "failed to send change password code")
return
}
response.Success(c, gin.H{"success": true})
}
// Logout 用户登出
func (h *UserHandler) Logout(c *gin.Context) {
currentUserID := c.GetString("user_id")
if currentUserID == "" {
response.Unauthorized(c, "not logged in")
return
}
ip := c.ClientIP()
userAgent := c.GetHeader("User-Agent")
// 获取用户信息
user, err := h.userService.GetUserByID(c.Request.Context(), currentUserID)
if err != nil {
response.InternalServerError(c, "user not found")
return
}
// 记录登出日志
if h.logService != nil {
h.logService.LoginLog.RecordLogin(&model.LoginLog{
UserID: user.ID,
UserName: user.Username,
NickName: user.Nickname,
Event: string(model.LoginEventLogout),
IP: ip,
UserAgent: userAgent,
Result: string(model.LoginResultSuccess),
})
}
response.Success(c, gin.H{"success": true})
}
// Search 搜索用户
func (h *UserHandler) Search(c *gin.Context) {
keyword := c.Query("keyword")
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
page, pageSize = normalizePagination(page, pageSize)
users, total, err := h.userService.Search(c.Request.Context(), keyword, page, pageSize)
if err != nil {
response.InternalServerError(c, "failed to search users")
return
}
// 获取实时计算的帖子数量
var userResponses []*dto.UserResponse
if len(users) > 0 {
userIDs := make([]string, len(users))
for i, u := range users {
userIDs[i] = u.ID
}
postsCountMap, _ := h.userService.GetUserPostCountBatch(c.Request.Context(), userIDs)
userResponses = dto.ConvertUsersToResponseWithMutualFollowAndPostsCount(users, nil, postsCountMap)
} else {
userResponses = dto.ConvertUsersToResponse(users)
}
response.Paginated(c, userResponses, total, page, pageSize)
}
// GetPrivacySettings 获取隐私设置
func (h *UserHandler) GetPrivacySettings(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
response.Unauthorized(c, "")
return
}
settings, err := h.userService.GetPrivacySettings(c.Request.Context(), userID)
if err != nil {
response.InternalServerError(c, "failed to get privacy settings")
return
}
response.Success(c, &dto.PrivacySettingsResponse{
FollowersVisibility: string(settings.FollowersVisibility),
FollowingVisibility: string(settings.FollowingVisibility),
PostsVisibility: string(settings.PostsVisibility),
FavoritesVisibility: string(settings.FavoritesVisibility),
})
}
// UpdatePrivacySettings 更新隐私设置
func (h *UserHandler) UpdatePrivacySettings(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
response.Unauthorized(c, "")
return
}
var req dto.PrivacySettingsRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
settings := &model.PrivacySettings{}
if req.FollowersVisibility != nil {
settings.FollowersVisibility = model.VisibilityLevel(*req.FollowersVisibility)
} else {
settings.FollowersVisibility = model.VisibilityEveryone
}
if req.FollowingVisibility != nil {
settings.FollowingVisibility = model.VisibilityLevel(*req.FollowingVisibility)
} else {
settings.FollowingVisibility = model.VisibilityEveryone
}
if req.PostsVisibility != nil {
settings.PostsVisibility = model.VisibilityLevel(*req.PostsVisibility)
} else {
settings.PostsVisibility = model.VisibilityEveryone
}
if req.FavoritesVisibility != nil {
settings.FavoritesVisibility = model.VisibilityLevel(*req.FavoritesVisibility)
} else {
settings.FavoritesVisibility = model.VisibilityEveryone
}
if err := h.userService.UpdatePrivacySettings(c.Request.Context(), userID, settings); err != nil {
response.InternalServerError(c, "failed to update privacy settings")
return
}
response.Success(c, &dto.PrivacySettingsResponse{
FollowersVisibility: string(settings.FollowersVisibility),
FollowingVisibility: string(settings.FollowingVisibility),
PostsVisibility: string(settings.PostsVisibility),
FavoritesVisibility: string(settings.FavoritesVisibility),
})
}
// RequestAccountDeletion 申请注销账号
func (h *UserHandler) RequestAccountDeletion(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
response.Unauthorized(c, "")
return
}
var req dto.RequestDeletionRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
if err := h.userService.RequestAccountDeletion(c.Request.Context(), userID, req.Password); err != nil {
response.HandleError(c, err, "failed to request account deletion")
return
}
isPending, requestedAt, daysLeft, err := h.userService.GetDeletionStatus(c.Request.Context(), userID)
if err != nil {
response.InternalServerError(c, "failed to get deletion status")
return
}
resp := &dto.DeletionStatusResponse{
IsPendingDeletion: isPending,
CoolDownDays: daysLeft,
}
if requestedAt != nil {
resp.DeletionRequestedAt = dto.FormatTime(*requestedAt)
}
response.Success(c, resp)
}
// CancelAccountDeletion 取消注销
func (h *UserHandler) CancelAccountDeletion(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
response.Unauthorized(c, "")
return
}
if err := h.userService.CancelAccountDeletion(c.Request.Context(), userID); err != nil {
response.InternalServerError(c, "failed to cancel account deletion")
return
}
response.Success(c, gin.H{"success": true})
}
// GetDeletionStatus 获取注销状态
func (h *UserHandler) GetDeletionStatus(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
response.Unauthorized(c, "")
return
}
isPending, requestedAt, daysLeft, err := h.userService.GetDeletionStatus(c.Request.Context(), userID)
if err != nil {
response.InternalServerError(c, "failed to get deletion status")
return
}
resp := &dto.DeletionStatusResponse{
IsPendingDeletion: isPending,
CoolDownDays: daysLeft,
}
if requestedAt != nil {
resp.DeletionRequestedAt = dto.FormatTime(*requestedAt)
}
response.Success(c, resp)
}