veza/veza-backend-api/internal/services/cloud_backup.go

83 lines
2 KiB
Go

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
}