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:
@@ -615,6 +615,17 @@ func (h *UserHandler) GetBlockStatus(c *gin.Context) {
|
||||
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")
|
||||
|
||||
@@ -654,6 +665,17 @@ func (h *UserHandler) GetFollowingList(c *gin.Context) {
|
||||
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")
|
||||
|
||||
@@ -810,3 +832,151 @@ func (h *UserHandler) Search(c *gin.Context) {
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user