mirror of
https://github.com/DeBrosOfficial/orama.git
synced 2026-03-17 20:46:58 +00:00
134 lines
4.1 KiB
Go
134 lines
4.1 KiB
Go
package gateway
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/DeBrosOfficial/network/pkg/logging"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// rqliteExportHandler handles GET /v1/rqlite/export
|
|
// Proxies to the namespace's RQLite /db/backup endpoint to download a raw SQLite snapshot.
|
|
// Protected by requiresNamespaceOwnership() via the /v1/rqlite/ prefix.
|
|
func (g *Gateway) rqliteExportHandler(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodGet {
|
|
writeError(w, http.StatusMethodNotAllowed, "method not allowed")
|
|
return
|
|
}
|
|
|
|
rqliteURL := g.rqliteBaseURL()
|
|
if rqliteURL == "" {
|
|
writeError(w, http.StatusServiceUnavailable, "RQLite not configured")
|
|
return
|
|
}
|
|
|
|
backupURL := rqliteURL + "/db/backup"
|
|
|
|
client := &http.Client{Timeout: 5 * time.Minute}
|
|
resp, err := client.Get(backupURL)
|
|
if err != nil {
|
|
g.logger.ComponentError(logging.ComponentGeneral, "rqlite export: failed to reach RQLite backup endpoint",
|
|
zap.String("url", backupURL), zap.Error(err))
|
|
writeError(w, http.StatusBadGateway, fmt.Sprintf("failed to reach RQLite: %v", err))
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
body, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
|
|
writeError(w, resp.StatusCode, fmt.Sprintf("RQLite backup failed: %s", string(body)))
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/octet-stream")
|
|
w.Header().Set("Content-Disposition", "attachment; filename=rqlite-export.db")
|
|
if resp.ContentLength > 0 {
|
|
w.Header().Set("Content-Length", fmt.Sprintf("%d", resp.ContentLength))
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
written, err := io.Copy(w, resp.Body)
|
|
if err != nil {
|
|
g.logger.ComponentError(logging.ComponentGeneral, "rqlite export: error streaming backup",
|
|
zap.Int64("bytes_written", written), zap.Error(err))
|
|
return
|
|
}
|
|
|
|
g.logger.ComponentInfo(logging.ComponentGeneral, "rqlite export completed", zap.Int64("bytes", written))
|
|
}
|
|
|
|
// rqliteImportHandler handles POST /v1/rqlite/import
|
|
// Proxies the request body (raw SQLite binary) to the namespace's RQLite /db/load endpoint.
|
|
// This is a DESTRUCTIVE operation that replaces the entire database.
|
|
// Protected by requiresNamespaceOwnership() via the /v1/rqlite/ prefix.
|
|
func (g *Gateway) rqliteImportHandler(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
writeError(w, http.StatusMethodNotAllowed, "method not allowed")
|
|
return
|
|
}
|
|
|
|
rqliteURL := g.rqliteBaseURL()
|
|
if rqliteURL == "" {
|
|
writeError(w, http.StatusServiceUnavailable, "RQLite not configured")
|
|
return
|
|
}
|
|
|
|
ct := r.Header.Get("Content-Type")
|
|
if !strings.HasPrefix(ct, "application/octet-stream") {
|
|
writeError(w, http.StatusBadRequest, "Content-Type must be application/octet-stream")
|
|
return
|
|
}
|
|
|
|
loadURL := rqliteURL + "/db/load"
|
|
|
|
proxyReq, err := http.NewRequestWithContext(r.Context(), http.MethodPost, loadURL, r.Body)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, fmt.Sprintf("failed to create proxy request: %v", err))
|
|
return
|
|
}
|
|
proxyReq.Header.Set("Content-Type", "application/octet-stream")
|
|
if r.ContentLength > 0 {
|
|
proxyReq.ContentLength = r.ContentLength
|
|
}
|
|
|
|
client := &http.Client{Timeout: 5 * time.Minute}
|
|
resp, err := client.Do(proxyReq)
|
|
if err != nil {
|
|
g.logger.ComponentError(logging.ComponentGeneral, "rqlite import: failed to reach RQLite load endpoint",
|
|
zap.String("url", loadURL), zap.Error(err))
|
|
writeError(w, http.StatusBadGateway, fmt.Sprintf("failed to reach RQLite: %v", err))
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
writeError(w, resp.StatusCode, fmt.Sprintf("RQLite load failed: %s", string(body)))
|
|
return
|
|
}
|
|
|
|
g.logger.ComponentInfo(logging.ComponentGeneral, "rqlite import completed successfully")
|
|
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"status": "ok",
|
|
"message": "database imported successfully",
|
|
})
|
|
}
|
|
|
|
// rqliteBaseURL returns the raw RQLite HTTP URL for proxying native API calls.
|
|
func (g *Gateway) rqliteBaseURL() string {
|
|
dsn := g.cfg.RQLiteDSN
|
|
if dsn == "" {
|
|
dsn = "http://localhost:5001"
|
|
}
|
|
if idx := strings.Index(dsn, "?"); idx != -1 {
|
|
dsn = dsn[:idx]
|
|
}
|
|
return strings.TrimRight(dsn, "/")
|
|
}
|