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:
@@ -44,6 +44,8 @@ type UserSelfResponse struct {
|
||||
FollowingCount int `json:"following_count"`
|
||||
IsVerified bool `json:"is_verified"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
// 隐私设置
|
||||
PrivacySettings *PrivacySettingsResponse `json:"privacy_settings,omitempty"`
|
||||
}
|
||||
|
||||
// UserDetailResponse 用户详情响应(仅本人可见,包含敏感信息)
|
||||
@@ -66,6 +68,8 @@ type UserDetailResponse struct {
|
||||
IsFollowing bool `json:"is_following"`
|
||||
IsFollowingMe bool `json:"is_following_me"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
// 隐私设置
|
||||
PrivacySettings *PrivacySettingsResponse `json:"privacy_settings,omitempty"`
|
||||
}
|
||||
|
||||
// ==================== Admin User DTOs ====================
|
||||
@@ -1403,3 +1407,33 @@ type AdminReviewVerificationRequest struct {
|
||||
Approve bool `json:"approve" binding:"required"`
|
||||
RejectReason string `json:"reject_reason" binding:"omitempty,max=500"`
|
||||
}
|
||||
|
||||
// ==================== Privacy & Account Deletion DTOs ====================
|
||||
|
||||
// PrivacySettingsRequest 隐私设置请求
|
||||
type PrivacySettingsRequest struct {
|
||||
FollowersVisibility *string `json:"followers_visibility" binding:"omitempty,oneof=everyone following self"`
|
||||
FollowingVisibility *string `json:"following_visibility" binding:"omitempty,oneof=everyone following self"`
|
||||
PostsVisibility *string `json:"posts_visibility" binding:"omitempty,oneof=everyone following self"`
|
||||
FavoritesVisibility *string `json:"favorites_visibility" binding:"omitempty,oneof=everyone following self"`
|
||||
}
|
||||
|
||||
// PrivacySettingsResponse 隐私设置响应
|
||||
type PrivacySettingsResponse struct {
|
||||
FollowersVisibility string `json:"followers_visibility"`
|
||||
FollowingVisibility string `json:"following_visibility"`
|
||||
PostsVisibility string `json:"posts_visibility"`
|
||||
FavoritesVisibility string `json:"favorites_visibility"`
|
||||
}
|
||||
|
||||
// RequestDeletionRequest 申请注销请求
|
||||
type RequestDeletionRequest struct {
|
||||
Password string `json:"password" binding:"required"`
|
||||
}
|
||||
|
||||
// DeletionStatusResponse 注销状态响应
|
||||
type DeletionStatusResponse struct {
|
||||
IsPendingDeletion bool `json:"is_pending_deletion"`
|
||||
DeletionRequestedAt string `json:"deletion_requested_at,omitempty"`
|
||||
CoolDownDays int `json:"cool_down_days,omitempty"`
|
||||
}
|
||||
|
||||
@@ -40,22 +40,35 @@ func ConvertUserToSelfResponse(user *model.User, postsCount int) *UserSelfRespon
|
||||
return nil
|
||||
}
|
||||
return &UserSelfResponse{
|
||||
ID: user.ID,
|
||||
Username: user.Username,
|
||||
Nickname: user.Nickname,
|
||||
Email: user.Email,
|
||||
Phone: user.Phone,
|
||||
EmailVerified: user.EmailVerified,
|
||||
Avatar: getAvatarOrDefault(user),
|
||||
CoverURL: user.CoverURL,
|
||||
Bio: user.Bio,
|
||||
Website: user.Website,
|
||||
Location: user.Location,
|
||||
PostsCount: postsCount,
|
||||
FollowersCount: user.FollowersCount,
|
||||
FollowingCount: user.FollowingCount,
|
||||
IsVerified: user.IsVerified,
|
||||
CreatedAt: FormatTime(user.CreatedAt),
|
||||
ID: user.ID,
|
||||
Username: user.Username,
|
||||
Nickname: user.Nickname,
|
||||
Email: user.Email,
|
||||
Phone: user.Phone,
|
||||
EmailVerified: user.EmailVerified,
|
||||
Avatar: getAvatarOrDefault(user),
|
||||
CoverURL: user.CoverURL,
|
||||
Bio: user.Bio,
|
||||
Website: user.Website,
|
||||
Location: user.Location,
|
||||
PostsCount: postsCount,
|
||||
FollowersCount: user.FollowersCount,
|
||||
FollowingCount: user.FollowingCount,
|
||||
IsVerified: user.IsVerified,
|
||||
CreatedAt: FormatTime(user.CreatedAt),
|
||||
PrivacySettings: convertPrivacySettingsToResponse(&user.PrivacySettings),
|
||||
}
|
||||
}
|
||||
|
||||
func convertPrivacySettingsToResponse(ps *model.PrivacySettings) *PrivacySettingsResponse {
|
||||
if ps == nil {
|
||||
return nil
|
||||
}
|
||||
return &PrivacySettingsResponse{
|
||||
FollowersVisibility: string(ps.FollowersVisibility),
|
||||
FollowingVisibility: string(ps.FollowingVisibility),
|
||||
PostsVisibility: string(ps.PostsVisibility),
|
||||
FavoritesVisibility: string(ps.FavoritesVisibility),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,21 +172,22 @@ func ConvertUserToDetailResponse(user *model.User) *UserDetailResponse {
|
||||
return nil
|
||||
}
|
||||
return &UserDetailResponse{
|
||||
ID: user.ID,
|
||||
Username: user.Username,
|
||||
Nickname: user.Nickname,
|
||||
Email: user.Email,
|
||||
EmailVerified: user.EmailVerified,
|
||||
Avatar: getAvatarOrDefault(user),
|
||||
CoverURL: user.CoverURL,
|
||||
Bio: user.Bio,
|
||||
Website: user.Website,
|
||||
Location: user.Location,
|
||||
PostsCount: user.PostsCount,
|
||||
FollowersCount: user.FollowersCount,
|
||||
FollowingCount: user.FollowingCount,
|
||||
IsVerified: user.IsVerified,
|
||||
CreatedAt: FormatTime(user.CreatedAt),
|
||||
ID: user.ID,
|
||||
Username: user.Username,
|
||||
Nickname: user.Nickname,
|
||||
Email: user.Email,
|
||||
EmailVerified: user.EmailVerified,
|
||||
Avatar: getAvatarOrDefault(user),
|
||||
CoverURL: user.CoverURL,
|
||||
Bio: user.Bio,
|
||||
Website: user.Website,
|
||||
Location: user.Location,
|
||||
PostsCount: user.PostsCount,
|
||||
FollowersCount: user.FollowersCount,
|
||||
FollowingCount: user.FollowingCount,
|
||||
IsVerified: user.IsVerified,
|
||||
CreatedAt: FormatTime(user.CreatedAt),
|
||||
PrivacySettings: convertPrivacySettingsToResponse(&user.PrivacySettings),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,22 +197,23 @@ func ConvertUserToDetailResponseWithPostsCount(user *model.User, postsCount int)
|
||||
return nil
|
||||
}
|
||||
return &UserDetailResponse{
|
||||
ID: user.ID,
|
||||
Username: user.Username,
|
||||
Nickname: user.Nickname,
|
||||
Email: user.Email,
|
||||
EmailVerified: user.EmailVerified,
|
||||
Phone: user.Phone,
|
||||
Avatar: getAvatarOrDefault(user),
|
||||
CoverURL: user.CoverURL,
|
||||
Bio: user.Bio,
|
||||
Website: user.Website,
|
||||
Location: user.Location,
|
||||
PostsCount: postsCount,
|
||||
FollowersCount: user.FollowersCount,
|
||||
FollowingCount: user.FollowingCount,
|
||||
IsVerified: user.IsVerified,
|
||||
CreatedAt: FormatTime(user.CreatedAt),
|
||||
ID: user.ID,
|
||||
Username: user.Username,
|
||||
Nickname: user.Nickname,
|
||||
Email: user.Email,
|
||||
EmailVerified: user.EmailVerified,
|
||||
Phone: user.Phone,
|
||||
Avatar: getAvatarOrDefault(user),
|
||||
CoverURL: user.CoverURL,
|
||||
Bio: user.Bio,
|
||||
Website: user.Website,
|
||||
Location: user.Location,
|
||||
PostsCount: postsCount,
|
||||
FollowersCount: user.FollowersCount,
|
||||
FollowingCount: user.FollowingCount,
|
||||
IsVerified: user.IsVerified,
|
||||
CreatedAt: FormatTime(user.CreatedAt),
|
||||
PrivacySettings: convertPrivacySettingsToResponse(&user.PrivacySettings),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user