package services import ( "context" "fmt" "time" "go.uber.org/zap" "gorm.io/gorm" "veza-backend-api/internal/models" ) // CloudBackupWorker copies cloud files to backup prefix periodically type CloudBackupWorker struct { db *gorm.DB s3Service *S3StorageService logger *zap.Logger interval time.Duration } // NewCloudBackupWorker creates a new backup worker func NewCloudBackupWorker(db *gorm.DB, s3Service *S3StorageService, logger *zap.Logger) *CloudBackupWorker { return &CloudBackupWorker{ db: db, s3Service: s3Service, logger: logger, interval: 24 * time.Hour, } } // Start runs the backup loop func (w *CloudBackupWorker) Start(ctx context.Context) { if w.s3Service == nil { w.logger.Info("Cloud backup worker: S3 not configured, skipping") return } ticker := time.NewTicker(w.interval) defer ticker.Stop() w.logger.Info("Cloud backup worker started", zap.Duration("interval", w.interval)) for { select { case <-ctx.Done(): return case <-ticker.C: if err := w.runBackup(ctx); err != nil { w.logger.Error("Cloud backup failed", zap.Error(err)) } } } } func (w *CloudBackupWorker) runBackup(ctx context.Context) error { prefix := "backup/" + time.Now().Format("2006-01-02") + "/" var files []models.UserFile if err := w.db.WithContext(ctx).Find(&files).Error; err != nil { return fmt.Errorf("list files: %w", err) } copied := 0 for _, f := range files { backupKey := prefix + f.S3Key data, err := w.s3Service.DownloadFile(ctx, f.S3Key) if err != nil { w.logger.Warn("backup: skip file", zap.String("key", f.S3Key), zap.Error(err)) continue } if _, err := w.s3Service.UploadFile(ctx, data, backupKey, f.MimeType); err != nil { w.logger.Warn("backup: upload failed", zap.String("key", backupKey), zap.Error(err)) continue } copied++ } if copied > 0 { w.logger.Info("Cloud backup completed", zap.Int("files_copied", copied), zap.String("prefix", prefix)) } return nil }