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:
109
internal/service/account_cleanup_service.go
Normal file
109
internal/service/account_cleanup_service.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"carrot_bbs/internal/model"
|
||||
"carrot_bbs/internal/repository"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const AccountDeletionCoolDownDays = 90
|
||||
|
||||
type AccountCleanupService interface {
|
||||
CleanupExpiredAccounts(ctx context.Context) (int64, error)
|
||||
}
|
||||
|
||||
type accountCleanupServiceImpl struct {
|
||||
db *gorm.DB
|
||||
userRepo repository.UserRepository
|
||||
logService *LogService
|
||||
}
|
||||
|
||||
func NewAccountCleanupService(
|
||||
db *gorm.DB,
|
||||
userRepo repository.UserRepository,
|
||||
logService *LogService,
|
||||
) AccountCleanupService {
|
||||
return &accountCleanupServiceImpl{
|
||||
db: db,
|
||||
userRepo: userRepo,
|
||||
logService: logService,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *accountCleanupServiceImpl) CleanupExpiredAccounts(ctx context.Context) (int64, error) {
|
||||
cutoffTime := time.Now().AddDate(0, 0, -AccountDeletionCoolDownDays)
|
||||
|
||||
users, err := s.userRepo.GetPendingDeletionUsers(cutoffTime)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if len(users) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
var deletedCount int64
|
||||
for _, user := range users {
|
||||
if err := s.anonymizeAndDeleteUser(ctx, user); err != nil {
|
||||
log.Printf("[AccountCleanup] failed to cleanup user %s: %v", user.ID, err)
|
||||
continue
|
||||
}
|
||||
deletedCount++
|
||||
}
|
||||
|
||||
log.Printf("[AccountCleanup] cleaned up %d expired accounts", deletedCount)
|
||||
return deletedCount, nil
|
||||
}
|
||||
|
||||
func (s *accountCleanupServiceImpl) anonymizeAndDeleteUser(ctx context.Context, user *model.User) error {
|
||||
deletedUserID := "deleted_user"
|
||||
|
||||
return s.db.Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Model(&model.Post{}).Where("user_id = ?", user.ID).Update("user_id", deletedUserID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tx.Model(&model.Comment{}).Where("user_id = ?", user.ID).Update("user_id", deletedUserID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tx.Where("follower_id = ? OR following_id = ?", user.ID, user.ID).Delete(&model.Follow{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tx.Where("user_id = ?", user.ID).Delete(&model.Favorite{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tx.Where("user_id = ?", user.ID).Delete(&model.PostLike{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tx.Where("user_id = ?", user.ID).Delete(&model.CommentLike{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tx.Unscoped().Delete(&model.User{}, "id = ?", user.ID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if s.logService != nil {
|
||||
s.logService.OperationLog.RecordOperation(&model.OperationLog{
|
||||
UserID: user.ID,
|
||||
UserName: user.Username,
|
||||
Operation: "account_deleted",
|
||||
Module: "user",
|
||||
TargetType: "user",
|
||||
TargetID: user.ID,
|
||||
Status: "success",
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user