package handlers import ( "encoding/json" "fmt" "io/ioutil" "net/http" "os" "os/exec" "path/filepath" "sort" "strings" "time" ) type BackupHandler struct { backupDir string } type BackupInfo struct { Filename string `json:"filename"` Size string `json:"size"` Date string `json:"date"` Timestamp string `json:"timestamp"` } func NewBackupHandler() *BackupHandler { // Usa o caminho montado no container backupDir := "/backups" // Garante que o diretório existe if _, err := os.Stat(backupDir); os.IsNotExist(err) { os.MkdirAll(backupDir, 0755) } return &BackupHandler{ backupDir: backupDir, } } // ListBackups lista todos os backups disponíveis func (h *BackupHandler) ListBackups(w http.ResponseWriter, r *http.Request) { files, err := ioutil.ReadDir(h.backupDir) if err != nil { http.Error(w, "Error reading backups directory", http.StatusInternalServerError) return } var backups []BackupInfo for _, file := range files { if strings.HasPrefix(file.Name(), "aggios_backup_") && strings.HasSuffix(file.Name(), ".sql") { // Extrai timestamp do nome do arquivo timestamp := strings.TrimPrefix(file.Name(), "aggios_backup_") timestamp = strings.TrimSuffix(timestamp, ".sql") // Formata a data t, _ := time.Parse("2006-01-02_15-04-05", timestamp) dateStr := t.Format("02/01/2006 15:04:05") // Formata o tamanho sizeMB := float64(file.Size()) / 1024 sizeStr := fmt.Sprintf("%.2f KB", sizeMB) backups = append(backups, BackupInfo{ Filename: file.Name(), Size: sizeStr, Date: dateStr, Timestamp: timestamp, }) } } // Ordena por data (mais recente primeiro) sort.Slice(backups, func(i, j int) bool { return backups[i].Timestamp > backups[j].Timestamp }) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "backups": backups, }) } // CreateBackup cria um novo backup do banco de dados func (h *BackupHandler) CreateBackup(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } timestamp := time.Now().Format("2006-01-02_15-04-05") filename := fmt.Sprintf("aggios_backup_%s.sql", timestamp) filepath := filepath.Join(h.backupDir, filename) // Usa pg_dump diretamente (backend e postgres estão na mesma rede docker) dbPassword := os.Getenv("DB_PASSWORD") if dbPassword == "" { dbPassword = "A9g10s_S3cur3_P@ssw0rd_2025!" } cmd := exec.Command("pg_dump", "-h", "postgres", "-U", "aggios", "-d", "aggios_db", "--no-password") // Define a variável de ambiente para a senha cmd.Env = append(os.Environ(), fmt.Sprintf("PGPASSWORD=%s", dbPassword)) output, err := cmd.Output() if err != nil { http.Error(w, fmt.Sprintf("Error creating backup: %v", err), http.StatusInternalServerError) return } // Salva o backup no arquivo err = ioutil.WriteFile(filepath, output, 0644) if err != nil { http.Error(w, fmt.Sprintf("Error saving backup: %v", err), http.StatusInternalServerError) return } // Limpa backups antigos (mantém apenas os últimos 10) h.cleanOldBackups() fileInfo, _ := os.Stat(filepath) sizeMB := float64(fileInfo.Size()) / 1024 sizeStr := fmt.Sprintf("%.2f KB", sizeMB) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "message": "Backup created successfully", "filename": filename, "size": sizeStr, }) } // RestoreBackup restaura um backup específico func (h *BackupHandler) RestoreBackup(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } var req struct { Filename string `json:"filename"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Invalid request", http.StatusBadRequest) return } if req.Filename == "" { http.Error(w, "Filename is required", http.StatusBadRequest) return } // Valida que o arquivo existe e está no diretório correto backupPath := filepath.Join(h.backupDir, req.Filename) if !strings.HasPrefix(backupPath, h.backupDir) { http.Error(w, "Invalid filename", http.StatusBadRequest) return } if _, err := os.Stat(backupPath); os.IsNotExist(err) { http.Error(w, "Backup file not found", http.StatusNotFound) return } // Lê o conteúdo do backup backupContent, err := ioutil.ReadFile(backupPath) if err != nil { http.Error(w, fmt.Sprintf("Error reading backup: %v", err), http.StatusInternalServerError) return } // Restaura o backup usando psql diretamente dbPassword := os.Getenv("DB_PASSWORD") if dbPassword == "" { dbPassword = "A9g10s_S3cur3_P@ssw0rd_2025!" } cmd := exec.Command("psql", "-h", "postgres", "-U", "aggios", "-d", "aggios_db", "--no-password") cmd.Stdin = strings.NewReader(string(backupContent)) cmd.Env = append(os.Environ(), fmt.Sprintf("PGPASSWORD=%s", dbPassword)) if err := cmd.Run(); err != nil { http.Error(w, fmt.Sprintf("Error restoring backup: %v", err), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "message": "Backup restored successfully", }) } // DownloadBackup permite fazer download de um backup func (h *BackupHandler) DownloadBackup(w http.ResponseWriter, r *http.Request) { // Extrai o filename da URL parts := strings.Split(r.URL.Path, "/") filename := parts[len(parts)-1] if filename == "" { http.Error(w, "Filename is required", http.StatusBadRequest) return } // Valida que o arquivo existe e está no diretório correto backupPath := filepath.Join(h.backupDir, filename) if !strings.HasPrefix(backupPath, h.backupDir) { http.Error(w, "Invalid filename", http.StatusBadRequest) return } if _, err := os.Stat(backupPath); os.IsNotExist(err) { http.Error(w, "Backup file not found", http.StatusNotFound) return } // Lê o arquivo data, err := ioutil.ReadFile(backupPath) if err != nil { http.Error(w, "Error reading file", http.StatusInternalServerError) return } // Define headers para download w.Header().Set("Content-Type", "application/sql") w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename)) w.Write(data) } // cleanOldBackups mantém apenas os últimos 10 backups func (h *BackupHandler) cleanOldBackups() { files, err := ioutil.ReadDir(h.backupDir) if err != nil { return } var backupFiles []os.FileInfo for _, file := range files { if strings.HasPrefix(file.Name(), "aggios_backup_") && strings.HasSuffix(file.Name(), ".sql") { backupFiles = append(backupFiles, file) } } // Ordena por data de modificação (mais recente primeiro) sort.Slice(backupFiles, func(i, j int) bool { return backupFiles[i].ModTime().After(backupFiles[j].ModTime()) }) // Remove backups antigos (mantém os 10 mais recentes) if len(backupFiles) > 10 { for _, file := range backupFiles[10:] { os.Remove(filepath.Join(h.backupDir, file.Name())) } } }