265 lines
7.0 KiB
Go
265 lines
7.0 KiB
Go
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()))
|
|
}
|
|
}
|
|
}
|