2026-04-28 14:53:04 +08:00
|
|
|
package service
|
2026-04-08 14:55:50 +08:00
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"time"
|
|
|
|
|
|
2026-04-22 16:01:59 +08:00
|
|
|
"with_you/internal/model"
|
|
|
|
|
"with_you/internal/repository"
|
2026-04-08 14:55:50 +08:00
|
|
|
|
2026-05-06 12:39:11 +08:00
|
|
|
"go.uber.org/zap"
|
2026-04-08 14:55:50 +08:00
|
|
|
"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 {
|
2026-05-06 12:39:11 +08:00
|
|
|
zap.L().Error("Failed to cleanup user",
|
|
|
|
|
zap.String("user_id", user.ID),
|
|
|
|
|
zap.Error(err),
|
|
|
|
|
)
|
2026-04-08 14:55:50 +08:00
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
deletedCount++
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-06 12:39:11 +08:00
|
|
|
zap.L().Info("Cleaned up expired accounts",
|
|
|
|
|
zap.Int64("count", deletedCount),
|
|
|
|
|
)
|
2026-04-08 14:55:50 +08:00
|
|
|
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
|
|
|
|
|
})
|
|
|
|
|
}
|