mirror of
https://github.com/DeBrosOfficial/network.git
synced 2026-01-30 22:03:03 +00:00
208 lines
5.4 KiB
Go
208 lines
5.4 KiB
Go
package sqlite
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/DeBrosOfficial/network/pkg/gateway/ctxkeys"
|
|
"github.com/DeBrosOfficial/network/pkg/ipfs"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// BackupHandler handles database backups
|
|
type BackupHandler struct {
|
|
sqliteHandler *SQLiteHandler
|
|
ipfsClient ipfs.IPFSClient
|
|
logger *zap.Logger
|
|
}
|
|
|
|
// NewBackupHandler creates a new backup handler
|
|
func NewBackupHandler(sqliteHandler *SQLiteHandler, ipfsClient ipfs.IPFSClient, logger *zap.Logger) *BackupHandler {
|
|
return &BackupHandler{
|
|
sqliteHandler: sqliteHandler,
|
|
ipfsClient: ipfsClient,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// BackupDatabase backs up a database to IPFS
|
|
func (h *BackupHandler) BackupDatabase(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
namespace, ok := ctx.Value(ctxkeys.NamespaceOverride).(string)
|
|
if !ok || namespace == "" {
|
|
http.Error(w, "Namespace not found in context", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
var req struct {
|
|
DatabaseName string `json:"database_name"`
|
|
}
|
|
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if req.DatabaseName == "" {
|
|
http.Error(w, "database_name is required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
h.logger.Info("Backing up database",
|
|
zap.String("namespace", namespace),
|
|
zap.String("database", req.DatabaseName),
|
|
)
|
|
|
|
// Get database metadata
|
|
dbMeta, err := h.sqliteHandler.getDatabaseRecord(ctx, namespace, req.DatabaseName)
|
|
if err != nil {
|
|
http.Error(w, "Database not found", http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
filePath := dbMeta["file_path"].(string)
|
|
|
|
// Check if file exists
|
|
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
|
http.Error(w, "Database file not found", http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
// Open file for reading
|
|
file, err := os.Open(filePath)
|
|
if err != nil {
|
|
h.logger.Error("Failed to open database file", zap.Error(err))
|
|
http.Error(w, "Failed to open database file", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
defer file.Close()
|
|
|
|
// Upload to IPFS
|
|
addResp, err := h.ipfsClient.Add(ctx, file, req.DatabaseName+".db")
|
|
if err != nil {
|
|
h.logger.Error("Failed to upload to IPFS", zap.Error(err))
|
|
http.Error(w, "Failed to backup database", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
cid := addResp.Cid
|
|
|
|
// Update backup metadata
|
|
now := time.Now()
|
|
query := `
|
|
UPDATE namespace_sqlite_databases
|
|
SET backup_cid = ?, last_backup_at = ?
|
|
WHERE namespace = ? AND database_name = ?
|
|
`
|
|
|
|
_, err = h.sqliteHandler.db.Exec(ctx, query, cid, now, namespace, req.DatabaseName)
|
|
if err != nil {
|
|
h.logger.Error("Failed to update backup metadata", zap.Error(err))
|
|
http.Error(w, "Failed to update backup metadata", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Record backup in history
|
|
h.recordBackup(ctx, dbMeta["id"].(string), cid)
|
|
|
|
h.logger.Info("Database backed up",
|
|
zap.String("namespace", namespace),
|
|
zap.String("database", req.DatabaseName),
|
|
zap.String("cid", cid),
|
|
)
|
|
|
|
// Return response
|
|
resp := map[string]interface{}{
|
|
"database_name": req.DatabaseName,
|
|
"backup_cid": cid,
|
|
"backed_up_at": now,
|
|
"ipfs_url": "https://ipfs.io/ipfs/" + cid,
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(resp)
|
|
}
|
|
|
|
// recordBackup records a backup in history
|
|
func (h *BackupHandler) recordBackup(ctx context.Context, dbID, cid string) {
|
|
query := `
|
|
INSERT INTO namespace_sqlite_backups (database_id, backup_cid, backed_up_at, size_bytes)
|
|
SELECT id, ?, ?, size_bytes FROM namespace_sqlite_databases WHERE id = ?
|
|
`
|
|
|
|
_, err := h.sqliteHandler.db.Exec(ctx, query, cid, time.Now(), dbID)
|
|
if err != nil {
|
|
h.logger.Error("Failed to record backup", zap.Error(err))
|
|
}
|
|
}
|
|
|
|
// ListBackups lists all backups for a database
|
|
func (h *BackupHandler) ListBackups(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
namespace, ok := ctx.Value(ctxkeys.NamespaceOverride).(string)
|
|
if !ok || namespace == "" {
|
|
http.Error(w, "Namespace not found in context", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
databaseName := r.URL.Query().Get("database_name")
|
|
if databaseName == "" {
|
|
http.Error(w, "database_name query parameter is required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Get database ID
|
|
dbMeta, err := h.sqliteHandler.getDatabaseRecord(ctx, namespace, databaseName)
|
|
if err != nil {
|
|
http.Error(w, "Database not found", http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
dbID := dbMeta["id"].(string)
|
|
|
|
// Query backups
|
|
type backupRow struct {
|
|
BackupCID string `db:"backup_cid"`
|
|
BackedUpAt time.Time `db:"backed_up_at"`
|
|
SizeBytes int64 `db:"size_bytes"`
|
|
}
|
|
|
|
var rows []backupRow
|
|
query := `
|
|
SELECT backup_cid, backed_up_at, size_bytes
|
|
FROM namespace_sqlite_backups
|
|
WHERE database_id = ?
|
|
ORDER BY backed_up_at DESC
|
|
LIMIT 50
|
|
`
|
|
|
|
err = h.sqliteHandler.db.Query(ctx, &rows, query, dbID)
|
|
if err != nil {
|
|
h.logger.Error("Failed to query backups", zap.Error(err))
|
|
http.Error(w, "Failed to query backups", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
backups := make([]map[string]interface{}, len(rows))
|
|
for i, row := range rows {
|
|
backups[i] = map[string]interface{}{
|
|
"backup_cid": row.BackupCID,
|
|
"backed_up_at": row.BackedUpAt,
|
|
"size_bytes": row.SizeBytes,
|
|
"ipfs_url": "https://ipfs.io/ipfs/" + row.BackupCID,
|
|
}
|
|
}
|
|
|
|
resp := map[string]interface{}{
|
|
"database_name": databaseName,
|
|
"backups": backups,
|
|
"total": len(backups),
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(resp)
|
|
}
|