mirror of
https://github.com/DeBrosOfficial/network.git
synced 2026-01-30 05:23:03 +00:00
fixed some more tests
This commit is contained in:
parent
0a7e3ba3c7
commit
903bef14a3
67
e2e/env.go
67
e2e/env.go
@ -1009,13 +1009,70 @@ func LoadTestEnv() (*E2ETestEnv, error) {
|
||||
}
|
||||
|
||||
// LoadTestEnvWithNamespace loads test environment with a specific namespace
|
||||
// It creates a new API key for the specified namespace to ensure proper isolation
|
||||
func LoadTestEnvWithNamespace(namespace string) (*E2ETestEnv, error) {
|
||||
env, err := LoadTestEnv()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
gatewayURL := os.Getenv("ORAMA_GATEWAY_URL")
|
||||
if gatewayURL == "" {
|
||||
gatewayURL = GetGatewayURL()
|
||||
}
|
||||
env.Namespace = namespace
|
||||
return env, nil
|
||||
|
||||
skipCleanup := os.Getenv("ORAMA_SKIP_CLEANUP") == "true"
|
||||
|
||||
// Generate a unique wallet address for this namespace
|
||||
// Using namespace as part of the wallet address for uniqueness
|
||||
wallet := fmt.Sprintf("0x%x", []byte(namespace+fmt.Sprintf("%d", time.Now().UnixNano())))
|
||||
if len(wallet) < 42 {
|
||||
wallet = wallet + strings.Repeat("0", 42-len(wallet))
|
||||
}
|
||||
if len(wallet) > 42 {
|
||||
wallet = wallet[:42]
|
||||
}
|
||||
|
||||
// Create an API key for this namespace via the simple-key endpoint
|
||||
reqBody := map[string]string{
|
||||
"wallet": wallet,
|
||||
"namespace": namespace,
|
||||
}
|
||||
bodyBytes, _ := json.Marshal(reqBody)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", gatewayURL+"/v1/auth/simple-key", bytes.NewReader(bodyBytes))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create API key request: %w", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
client := NewHTTPClient(10 * time.Second)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create API key: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
bodyBytes, _ := io.ReadAll(resp.Body)
|
||||
return nil, fmt.Errorf("API key creation failed with status %d: %s", resp.StatusCode, string(bodyBytes))
|
||||
}
|
||||
|
||||
var apiKeyResp map[string]interface{}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&apiKeyResp); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode API key response: %w", err)
|
||||
}
|
||||
|
||||
apiKey, ok := apiKeyResp["api_key"].(string)
|
||||
if !ok || apiKey == "" {
|
||||
return nil, fmt.Errorf("API key not found in response")
|
||||
}
|
||||
|
||||
return &E2ETestEnv{
|
||||
GatewayURL: gatewayURL,
|
||||
APIKey: apiKey,
|
||||
Namespace: namespace,
|
||||
HTTPClient: NewHTTPClient(30 * time.Second),
|
||||
SkipCleanup: skipCleanup,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateTestDeployment creates a test deployment and returns its ID
|
||||
|
||||
@ -112,8 +112,11 @@ func TestNamespaceIsolation_Deployments(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNamespaceIsolation_SQLiteDatabases(t *testing.T) {
|
||||
envA, _ := LoadTestEnvWithNamespace("namespace-a-" + fmt.Sprintf("%d", time.Now().Unix()))
|
||||
envB, _ := LoadTestEnvWithNamespace("namespace-b-" + fmt.Sprintf("%d", time.Now().Unix()))
|
||||
envA, err := LoadTestEnvWithNamespace("namespace-a-" + fmt.Sprintf("%d", time.Now().Unix()))
|
||||
require.NoError(t, err, "Should create test environment for namespace-a")
|
||||
|
||||
envB, err := LoadTestEnvWithNamespace("namespace-b-" + fmt.Sprintf("%d", time.Now().Unix()))
|
||||
require.NoError(t, err, "Should create test environment for namespace-b")
|
||||
|
||||
// Create database in namespace-a
|
||||
dbNameA := "users-db-a"
|
||||
@ -198,8 +201,11 @@ func TestNamespaceIsolation_SQLiteDatabases(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNamespaceIsolation_IPFSContent(t *testing.T) {
|
||||
envA, _ := LoadTestEnvWithNamespace("namespace-a-" + fmt.Sprintf("%d", time.Now().Unix()))
|
||||
envB, _ := LoadTestEnvWithNamespace("namespace-b-" + fmt.Sprintf("%d", time.Now().Unix()))
|
||||
envA, err := LoadTestEnvWithNamespace("namespace-a-" + fmt.Sprintf("%d", time.Now().Unix()))
|
||||
require.NoError(t, err, "Should create test environment for namespace-a")
|
||||
|
||||
envB, err := LoadTestEnvWithNamespace("namespace-b-" + fmt.Sprintf("%d", time.Now().Unix()))
|
||||
require.NoError(t, err, "Should create test environment for namespace-b")
|
||||
|
||||
// Upload file in namespace-a
|
||||
cidA := UploadTestFile(t, envA, "test-file-a.txt", "Content from namespace A")
|
||||
@ -294,21 +300,26 @@ func TestNamespaceIsolation_IPFSContent(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNamespaceIsolation_OlricCache(t *testing.T) {
|
||||
envA, _ := LoadTestEnvWithNamespace("namespace-a-" + fmt.Sprintf("%d", time.Now().Unix()))
|
||||
envB, _ := LoadTestEnvWithNamespace("namespace-b-" + fmt.Sprintf("%d", time.Now().Unix()))
|
||||
envA, err := LoadTestEnvWithNamespace("namespace-a-" + fmt.Sprintf("%d", time.Now().Unix()))
|
||||
require.NoError(t, err, "Should create test environment for namespace-a")
|
||||
|
||||
envB, err := LoadTestEnvWithNamespace("namespace-b-" + fmt.Sprintf("%d", time.Now().Unix()))
|
||||
require.NoError(t, err, "Should create test environment for namespace-b")
|
||||
|
||||
dmap := "test-cache"
|
||||
keyA := "user-session-123"
|
||||
valueA := `{"user_id": "alice", "token": "secret-token-a"}`
|
||||
|
||||
t.Run("Namespace-A sets cache key", func(t *testing.T) {
|
||||
reqBody := map[string]interface{}{
|
||||
"dmap": dmap,
|
||||
"key": keyA,
|
||||
"value": valueA,
|
||||
"ttl": 300,
|
||||
"ttl": "300s",
|
||||
}
|
||||
bodyBytes, _ := json.Marshal(reqBody)
|
||||
|
||||
req, _ := http.NewRequest("POST", envA.GatewayURL+"/v1/cache/set", bytes.NewReader(bodyBytes))
|
||||
req, _ := http.NewRequest("POST", envA.GatewayURL+"/v1/cache/put", bytes.NewReader(bodyBytes))
|
||||
req.Header.Set("Authorization", "Bearer "+envA.APIKey)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
@ -322,8 +333,15 @@ func TestNamespaceIsolation_OlricCache(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Namespace-B cannot GET Namespace-A cache key", func(t *testing.T) {
|
||||
req, _ := http.NewRequest("GET", envB.GatewayURL+"/v1/cache/get?key="+keyA, nil)
|
||||
reqBody := map[string]interface{}{
|
||||
"dmap": dmap,
|
||||
"key": keyA,
|
||||
}
|
||||
bodyBytes, _ := json.Marshal(reqBody)
|
||||
|
||||
req, _ := http.NewRequest("POST", envB.GatewayURL+"/v1/cache/get", bytes.NewReader(bodyBytes))
|
||||
req.Header.Set("Authorization", "Bearer "+envB.APIKey)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := envB.HTTPClient.Do(req)
|
||||
require.NoError(t, err, "Should execute request")
|
||||
@ -336,7 +354,10 @@ func TestNamespaceIsolation_OlricCache(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Namespace-B cannot DELETE Namespace-A cache key", func(t *testing.T) {
|
||||
reqBody := map[string]string{"key": keyA}
|
||||
reqBody := map[string]string{
|
||||
"dmap": dmap,
|
||||
"key": keyA,
|
||||
}
|
||||
bodyBytes, _ := json.Marshal(reqBody)
|
||||
|
||||
req, _ := http.NewRequest("POST", envB.GatewayURL+"/v1/cache/delete", bytes.NewReader(bodyBytes))
|
||||
@ -351,8 +372,15 @@ func TestNamespaceIsolation_OlricCache(t *testing.T) {
|
||||
assert.Contains(t, []int{http.StatusOK, http.StatusNotFound}, resp.StatusCode)
|
||||
|
||||
// Verify key still exists for namespace-a
|
||||
req2, _ := http.NewRequest("GET", envA.GatewayURL+"/v1/cache/get?key="+keyA, nil)
|
||||
reqBody2 := map[string]interface{}{
|
||||
"dmap": dmap,
|
||||
"key": keyA,
|
||||
}
|
||||
bodyBytes2, _ := json.Marshal(reqBody2)
|
||||
|
||||
req2, _ := http.NewRequest("POST", envA.GatewayURL+"/v1/cache/get", bytes.NewReader(bodyBytes2))
|
||||
req2.Header.Set("Authorization", "Bearer "+envA.APIKey)
|
||||
req2.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp2, err := envA.HTTPClient.Do(req2)
|
||||
require.NoError(t, err, "Should execute request")
|
||||
@ -361,10 +389,13 @@ func TestNamespaceIsolation_OlricCache(t *testing.T) {
|
||||
assert.Equal(t, http.StatusOK, resp2.StatusCode, "Key should still exist in namespace A")
|
||||
|
||||
var result map[string]interface{}
|
||||
bodyBytes2, _ := io.ReadAll(resp2.Body)
|
||||
require.NoError(t, json.Unmarshal(bodyBytes2, &result), "Should decode result")
|
||||
bodyBytes3, _ := io.ReadAll(resp2.Body)
|
||||
require.NoError(t, json.Unmarshal(bodyBytes3, &result), "Should decode result")
|
||||
|
||||
assert.Equal(t, valueA, result["value"], "Value should match")
|
||||
// Parse expected JSON string for comparison
|
||||
var expectedValue map[string]interface{}
|
||||
json.Unmarshal([]byte(valueA), &expectedValue)
|
||||
assert.Equal(t, expectedValue, result["value"], "Value should match")
|
||||
|
||||
t.Logf("✓ Namespace B cannot DELETE Namespace A cache key")
|
||||
})
|
||||
@ -374,13 +405,14 @@ func TestNamespaceIsolation_OlricCache(t *testing.T) {
|
||||
valueB := `{"user_id": "bob", "token": "secret-token-b"}`
|
||||
|
||||
reqBody := map[string]interface{}{
|
||||
"dmap": dmap,
|
||||
"key": keyA, // Same key name as namespace-a
|
||||
"value": valueB,
|
||||
"ttl": 300,
|
||||
"ttl": "300s",
|
||||
}
|
||||
bodyBytes, _ := json.Marshal(reqBody)
|
||||
|
||||
req, _ := http.NewRequest("POST", envB.GatewayURL+"/v1/cache/set", bytes.NewReader(bodyBytes))
|
||||
req, _ := http.NewRequest("POST", envB.GatewayURL+"/v1/cache/put", bytes.NewReader(bodyBytes))
|
||||
req.Header.Set("Authorization", "Bearer "+envB.APIKey)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
@ -391,8 +423,15 @@ func TestNamespaceIsolation_OlricCache(t *testing.T) {
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode, "Should set key in namespace B")
|
||||
|
||||
// Verify namespace-a still has their value
|
||||
req2, _ := http.NewRequest("GET", envA.GatewayURL+"/v1/cache/get?key="+keyA, nil)
|
||||
reqBody2 := map[string]interface{}{
|
||||
"dmap": dmap,
|
||||
"key": keyA,
|
||||
}
|
||||
bodyBytes2, _ := json.Marshal(reqBody2)
|
||||
|
||||
req2, _ := http.NewRequest("POST", envA.GatewayURL+"/v1/cache/get", bytes.NewReader(bodyBytes2))
|
||||
req2.Header.Set("Authorization", "Bearer "+envA.APIKey)
|
||||
req2.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp2, _ := envA.HTTPClient.Do(req2)
|
||||
defer resp2.Body.Close()
|
||||
@ -401,11 +440,21 @@ func TestNamespaceIsolation_OlricCache(t *testing.T) {
|
||||
bodyBytesA, _ := io.ReadAll(resp2.Body)
|
||||
require.NoError(t, json.Unmarshal(bodyBytesA, &resultA), "Should decode result A")
|
||||
|
||||
assert.Equal(t, valueA, resultA["value"], "Namespace A value should be unchanged")
|
||||
// Parse expected JSON string for comparison
|
||||
var expectedValueA map[string]interface{}
|
||||
json.Unmarshal([]byte(valueA), &expectedValueA)
|
||||
assert.Equal(t, expectedValueA, resultA["value"], "Namespace A value should be unchanged")
|
||||
|
||||
// Verify namespace-b has their different value
|
||||
req3, _ := http.NewRequest("GET", envB.GatewayURL+"/v1/cache/get?key="+keyA, nil)
|
||||
reqBody3 := map[string]interface{}{
|
||||
"dmap": dmap,
|
||||
"key": keyA,
|
||||
}
|
||||
bodyBytes3, _ := json.Marshal(reqBody3)
|
||||
|
||||
req3, _ := http.NewRequest("POST", envB.GatewayURL+"/v1/cache/get", bytes.NewReader(bodyBytes3))
|
||||
req3.Header.Set("Authorization", "Bearer "+envB.APIKey)
|
||||
req3.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp3, _ := envB.HTTPClient.Do(req3)
|
||||
defer resp3.Body.Close()
|
||||
@ -414,7 +463,10 @@ func TestNamespaceIsolation_OlricCache(t *testing.T) {
|
||||
bodyBytesB, _ := io.ReadAll(resp3.Body)
|
||||
require.NoError(t, json.Unmarshal(bodyBytesB, &resultB), "Should decode result B")
|
||||
|
||||
assert.Equal(t, valueB, resultB["value"], "Namespace B value should be different")
|
||||
// Parse expected JSON string for comparison
|
||||
var expectedValueB map[string]interface{}
|
||||
json.Unmarshal([]byte(valueB), &expectedValueB)
|
||||
assert.Equal(t, expectedValueB, resultB["value"], "Namespace B value should be different")
|
||||
|
||||
t.Logf("✓ Namespace B can set same key name independently")
|
||||
t.Logf(" - Namespace A value: %s", valueA)
|
||||
|
||||
@ -43,16 +43,20 @@ func PerformSimpleAuthentication(gatewayURL string) (*Credentials, error) {
|
||||
return nil, fmt.Errorf("invalid wallet address format")
|
||||
}
|
||||
|
||||
// Read namespace (optional)
|
||||
fmt.Print("Enter namespace (press Enter for 'default'): ")
|
||||
nsInput, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read namespace: %w", err)
|
||||
}
|
||||
// Read namespace (required)
|
||||
var namespace string
|
||||
for {
|
||||
fmt.Print("Enter namespace (required): ")
|
||||
nsInput, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read namespace: %w", err)
|
||||
}
|
||||
|
||||
namespace := strings.TrimSpace(nsInput)
|
||||
if namespace == "" {
|
||||
namespace = "default"
|
||||
namespace = strings.TrimSpace(nsInput)
|
||||
if namespace != "" {
|
||||
break
|
||||
}
|
||||
fmt.Println("⚠️ Namespace cannot be empty. Please enter a namespace.")
|
||||
}
|
||||
|
||||
fmt.Printf("\n✅ Wallet: %s\n", wallet)
|
||||
|
||||
@ -283,6 +283,7 @@ func initializeIPFS(logger *logging.ColoredLogger, cfg *Config, deps *Dependenci
|
||||
|
||||
ipfsCfg := ipfs.Config{
|
||||
ClusterAPIURL: ipfsClusterURL,
|
||||
IPFSAPIURL: ipfsAPIURL,
|
||||
Timeout: ipfsTimeout,
|
||||
}
|
||||
|
||||
|
||||
10
pkg/gateway/handlers/cache/delete_handler.go
vendored
10
pkg/gateway/handlers/cache/delete_handler.go
vendored
@ -55,8 +55,16 @@ func (h *CacheHandlers) DeleteHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Namespace isolation: prefix dmap with namespace
|
||||
namespace := getNamespaceFromContext(ctx)
|
||||
if namespace == "" {
|
||||
writeError(w, http.StatusUnauthorized, "namespace not found in context")
|
||||
return
|
||||
}
|
||||
namespacedDMap := fmt.Sprintf("%s:%s", namespace, req.DMap)
|
||||
|
||||
olricCluster := h.olricClient.GetClient()
|
||||
dm, err := olricCluster.NewDMap(req.DMap)
|
||||
dm, err := olricCluster.NewDMap(namespacedDMap)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, fmt.Sprintf("failed to create DMap: %v", err))
|
||||
return
|
||||
|
||||
20
pkg/gateway/handlers/cache/get_handler.go
vendored
20
pkg/gateway/handlers/cache/get_handler.go
vendored
@ -57,8 +57,16 @@ func (h *CacheHandlers) GetHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Namespace isolation: prefix dmap with namespace
|
||||
namespace := getNamespaceFromContext(ctx)
|
||||
if namespace == "" {
|
||||
writeError(w, http.StatusUnauthorized, "namespace not found in context")
|
||||
return
|
||||
}
|
||||
namespacedDMap := fmt.Sprintf("%s:%s", namespace, req.DMap)
|
||||
|
||||
olricCluster := h.olricClient.GetClient()
|
||||
dm, err := olricCluster.NewDMap(req.DMap)
|
||||
dm, err := olricCluster.NewDMap(namespacedDMap)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, fmt.Sprintf("failed to create DMap: %v", err))
|
||||
return
|
||||
@ -146,8 +154,16 @@ func (h *CacheHandlers) MultiGetHandler(w http.ResponseWriter, r *http.Request)
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Namespace isolation: prefix dmap with namespace
|
||||
namespace := getNamespaceFromContext(ctx)
|
||||
if namespace == "" {
|
||||
writeError(w, http.StatusUnauthorized, "namespace not found in context")
|
||||
return
|
||||
}
|
||||
namespacedDMap := fmt.Sprintf("%s:%s", namespace, req.DMap)
|
||||
|
||||
olricCluster := h.olricClient.GetClient()
|
||||
dm, err := olricCluster.NewDMap(req.DMap)
|
||||
dm, err := olricCluster.NewDMap(namespacedDMap)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, fmt.Sprintf("failed to create DMap: %v", err))
|
||||
return
|
||||
|
||||
10
pkg/gateway/handlers/cache/list_handler.go
vendored
10
pkg/gateway/handlers/cache/list_handler.go
vendored
@ -54,8 +54,16 @@ func (h *CacheHandlers) ScanHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Namespace isolation: prefix dmap with namespace
|
||||
namespace := getNamespaceFromContext(ctx)
|
||||
if namespace == "" {
|
||||
writeError(w, http.StatusUnauthorized, "namespace not found in context")
|
||||
return
|
||||
}
|
||||
namespacedDMap := fmt.Sprintf("%s:%s", namespace, req.DMap)
|
||||
|
||||
olricCluster := h.olricClient.GetClient()
|
||||
dm, err := olricCluster.NewDMap(req.DMap)
|
||||
dm, err := olricCluster.NewDMap(namespacedDMap)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, fmt.Sprintf("failed to create DMap: %v", err))
|
||||
return
|
||||
|
||||
20
pkg/gateway/handlers/cache/set_handler.go
vendored
20
pkg/gateway/handlers/cache/set_handler.go
vendored
@ -7,8 +7,18 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/DeBrosOfficial/network/pkg/gateway/ctxkeys"
|
||||
)
|
||||
|
||||
// getNamespaceFromContext extracts the namespace from the request context
|
||||
func getNamespaceFromContext(ctx context.Context) string {
|
||||
if ns, ok := ctx.Value(ctxkeys.NamespaceOverride).(string); ok {
|
||||
return ns
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// SetHandler handles cache PUT/SET requests for storing a key-value pair in a distributed map.
|
||||
// It expects a JSON body with "dmap", "key", and "value" fields, and optionally "ttl".
|
||||
// The value can be any JSON-serializable type (string, number, object, array, etc.).
|
||||
@ -60,8 +70,16 @@ func (h *CacheHandlers) SetHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Namespace isolation: prefix dmap with namespace
|
||||
namespace := getNamespaceFromContext(ctx)
|
||||
if namespace == "" {
|
||||
writeError(w, http.StatusUnauthorized, "namespace not found in context")
|
||||
return
|
||||
}
|
||||
namespacedDMap := fmt.Sprintf("%s:%s", namespace, req.DMap)
|
||||
|
||||
olricCluster := h.olricClient.GetClient()
|
||||
dm, err := olricCluster.NewDMap(req.DMap)
|
||||
dm, err := olricCluster.NewDMap(namespacedDMap)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, fmt.Sprintf("failed to create DMap: %v", err))
|
||||
return
|
||||
|
||||
@ -92,6 +92,7 @@ func (h *StaticDeploymentHandler) HandleUpload(w http.ResponseWriter, r *http.Re
|
||||
)
|
||||
|
||||
// Extract tarball to temporary directory
|
||||
// Create a wrapper directory so IPFS creates a root CID
|
||||
tmpDir, err := os.MkdirTemp("", "static-deploy-*")
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to create temp directory", zap.Error(err))
|
||||
@ -100,13 +101,21 @@ func (h *StaticDeploymentHandler) HandleUpload(w http.ResponseWriter, r *http.Re
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
if err := extractTarball(file, tmpDir); err != nil {
|
||||
// Extract into a subdirectory called "site" so we get a root directory CID
|
||||
siteDir := filepath.Join(tmpDir, "site")
|
||||
if err := os.MkdirAll(siteDir, 0755); err != nil {
|
||||
h.logger.Error("Failed to create site directory", zap.Error(err))
|
||||
http.Error(w, "Failed to process tarball", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if err := extractTarball(file, siteDir); err != nil {
|
||||
h.logger.Error("Failed to extract tarball", zap.Error(err))
|
||||
http.Error(w, "Failed to extract tarball", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Upload extracted directory to IPFS
|
||||
// Upload the parent directory (tmpDir) to IPFS, which will create a CID for the "site" subdirectory
|
||||
addResp, err := h.ipfsClient.AddDirectory(ctx, tmpDir)
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to upload to IPFS", zap.Error(err))
|
||||
|
||||
@ -7,6 +7,7 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/DeBrosOfficial/network/pkg/gateway/ctxkeys"
|
||||
"github.com/DeBrosOfficial/network/pkg/ipfs"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
@ -30,7 +31,11 @@ func NewBackupHandler(sqliteHandler *SQLiteHandler, ipfsClient ipfs.IPFSClient,
|
||||
// BackupDatabase backs up a database to IPFS
|
||||
func (h *BackupHandler) BackupDatabase(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
namespace := ctx.Value("namespace").(string)
|
||||
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"`
|
||||
@ -137,7 +142,11 @@ func (h *BackupHandler) recordBackup(ctx context.Context, dbID, cid string) {
|
||||
// ListBackups lists all backups for a database
|
||||
func (h *BackupHandler) ListBackups(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
namespace := ctx.Value("namespace").(string)
|
||||
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 == "" {
|
||||
|
||||
@ -11,6 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/DeBrosOfficial/network/pkg/deployments"
|
||||
"github.com/DeBrosOfficial/network/pkg/gateway/ctxkeys"
|
||||
"github.com/DeBrosOfficial/network/pkg/rqlite"
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
@ -27,18 +28,31 @@ type SQLiteHandler struct {
|
||||
|
||||
// NewSQLiteHandler creates a new SQLite handler
|
||||
func NewSQLiteHandler(db rqlite.Client, homeNodeManager *deployments.HomeNodeManager, logger *zap.Logger) *SQLiteHandler {
|
||||
// Use user's home directory for cross-platform compatibility
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
logger.Error("Failed to get user home directory", zap.Error(err))
|
||||
homeDir = os.Getenv("HOME")
|
||||
}
|
||||
|
||||
basePath := filepath.Join(homeDir, ".orama", "sqlite")
|
||||
|
||||
return &SQLiteHandler{
|
||||
db: db,
|
||||
homeNodeManager: homeNodeManager,
|
||||
logger: logger,
|
||||
basePath: "/home/debros/.orama/data/sqlite",
|
||||
basePath: basePath,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateDatabase creates a new SQLite database for a namespace
|
||||
func (h *SQLiteHandler) CreateDatabase(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
namespace := ctx.Value("namespace").(string)
|
||||
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"`
|
||||
|
||||
@ -1,12 +1,14 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/DeBrosOfficial/network/pkg/gateway/ctxkeys"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@ -29,7 +31,11 @@ type QueryResponse struct {
|
||||
// QueryDatabase executes a SQL query on a namespace database
|
||||
func (h *SQLiteHandler) QueryDatabase(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
namespace := ctx.Value("namespace").(string)
|
||||
namespace, ok := ctx.Value(ctxkeys.NamespaceOverride).(string)
|
||||
if !ok || namespace == "" {
|
||||
http.Error(w, "Namespace not found in context", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
var req QueryRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
@ -179,7 +185,8 @@ func (h *SQLiteHandler) updateDatabaseSize(namespace, databaseName, filePath str
|
||||
}
|
||||
|
||||
query := `UPDATE namespace_sqlite_databases SET size_bytes = ? WHERE namespace = ? AND database_name = ?`
|
||||
_, err = h.db.Exec(nil, query, stat.Size(), namespace, databaseName)
|
||||
ctx := context.Background()
|
||||
_, err = h.db.Exec(ctx, query, stat.Size(), namespace, databaseName)
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to update database size", zap.Error(err))
|
||||
}
|
||||
@ -199,7 +206,11 @@ type DatabaseInfo struct {
|
||||
// ListDatabases lists all databases for a namespace
|
||||
func (h *SQLiteHandler) ListDatabases(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
namespace := ctx.Value("namespace").(string)
|
||||
namespace, ok := ctx.Value(ctxkeys.NamespaceOverride).(string)
|
||||
if !ok || namespace == "" {
|
||||
http.Error(w, "Namespace not found in context", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
var databases []DatabaseInfo
|
||||
query := `
|
||||
|
||||
@ -459,9 +459,13 @@ func (g *Gateway) domainRoutingMiddleware(next http.Handler) http.Handler {
|
||||
|
||||
// Try to find deployment by domain
|
||||
deployment, err := g.getDeploymentByDomain(r.Context(), host)
|
||||
if err != nil || deployment == nil {
|
||||
// Not a deployment domain, continue to normal routing
|
||||
next.ServeHTTP(w, r)
|
||||
if err != nil {
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if deployment == nil {
|
||||
// Domain matches .orama.network but no deployment found
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@ -33,9 +33,10 @@ type IPFSClient interface {
|
||||
|
||||
// Client wraps an IPFS Cluster HTTP API client for storage operations
|
||||
type Client struct {
|
||||
apiURL string
|
||||
httpClient *http.Client
|
||||
logger *zap.Logger
|
||||
apiURL string
|
||||
ipfsAPIURL string
|
||||
httpClient *http.Client
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// Config holds configuration for the IPFS client
|
||||
@ -44,6 +45,10 @@ type Config struct {
|
||||
// If empty, defaults to "http://localhost:9094"
|
||||
ClusterAPIURL string
|
||||
|
||||
// IPFSAPIURL is the base URL for IPFS daemon API (e.g., "http://localhost:4501")
|
||||
// Used for operations that require IPFS daemon directly (like directory uploads)
|
||||
IPFSAPIURL string
|
||||
|
||||
// Timeout is the timeout for client operations
|
||||
// If zero, defaults to 60 seconds
|
||||
Timeout time.Duration
|
||||
@ -68,6 +73,14 @@ type AddResponse struct {
|
||||
Size int64 `json:"size"`
|
||||
}
|
||||
|
||||
// ipfsDaemonAddResponse represents the response from IPFS daemon's /add endpoint
|
||||
// The daemon returns Size as a string, unlike Cluster which returns it as int64
|
||||
type ipfsDaemonAddResponse struct {
|
||||
Name string `json:"Name"`
|
||||
Hash string `json:"Hash"` // Daemon uses "Hash" instead of "Cid"
|
||||
Size string `json:"Size"` // Daemon returns size as string
|
||||
}
|
||||
|
||||
// PinResponse represents the response from pinning a CID
|
||||
type PinResponse struct {
|
||||
Cid string `json:"cid"`
|
||||
@ -81,6 +94,11 @@ func NewClient(cfg Config, logger *zap.Logger) (*Client, error) {
|
||||
apiURL = "http://localhost:9094"
|
||||
}
|
||||
|
||||
ipfsAPIURL := cfg.IPFSAPIURL
|
||||
if ipfsAPIURL == "" {
|
||||
ipfsAPIURL = "http://localhost:4501"
|
||||
}
|
||||
|
||||
timeout := cfg.Timeout
|
||||
if timeout == 0 {
|
||||
timeout = 60 * time.Second
|
||||
@ -92,6 +110,7 @@ func NewClient(cfg Config, logger *zap.Logger) (*Client, error) {
|
||||
|
||||
return &Client{
|
||||
apiURL: apiURL,
|
||||
ipfsAPIURL: ipfsAPIURL,
|
||||
httpClient: httpClient,
|
||||
logger: logger,
|
||||
}, nil
|
||||
@ -240,23 +259,26 @@ func (c *Client) Add(ctx context.Context, reader io.Reader, name string) (*AddRe
|
||||
}
|
||||
|
||||
// AddDirectory adds all files in a directory to IPFS and returns the root directory CID
|
||||
// Uses IPFS daemon's multipart upload to preserve directory structure
|
||||
func (c *Client) AddDirectory(ctx context.Context, dirPath string) (*AddResponse, error) {
|
||||
var buf bytes.Buffer
|
||||
writer := multipart.NewWriter(&buf)
|
||||
|
||||
// Walk directory and add all files to multipart request
|
||||
var totalSize int64
|
||||
var fileCount int
|
||||
|
||||
// Walk directory and add all files to multipart request
|
||||
err := filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Skip directories
|
||||
// Skip directories themselves (IPFS will create them from file paths)
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get relative path
|
||||
// Get relative path from dirPath
|
||||
relPath, err := filepath.Rel(dirPath, path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get relative path: %w", err)
|
||||
@ -269,8 +291,9 @@ func (c *Client) AddDirectory(ctx context.Context, dirPath string) (*AddResponse
|
||||
}
|
||||
|
||||
totalSize += int64(len(data))
|
||||
fileCount++
|
||||
|
||||
// Add file to multipart
|
||||
// Add file to multipart with relative path
|
||||
part, err := writer.CreateFormFile("file", relPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create form file: %w", err)
|
||||
@ -287,14 +310,19 @@ func (c *Client) AddDirectory(ctx context.Context, dirPath string) (*AddResponse
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if fileCount == 0 {
|
||||
return nil, fmt.Errorf("no files found in directory")
|
||||
}
|
||||
|
||||
if err := writer.Close(); err != nil {
|
||||
return nil, fmt.Errorf("failed to close writer: %w", err)
|
||||
}
|
||||
|
||||
// Add with wrap-in-directory to create a root directory node
|
||||
apiURL := c.apiURL + "/add?wrap-in-directory=true"
|
||||
// Upload to IPFS daemon (not Cluster) with wrap-in-directory
|
||||
// This creates a UnixFS directory structure
|
||||
ipfsDaemonURL := c.ipfsAPIURL + "/api/v0/add?wrap-in-directory=true"
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", apiURL, &buf)
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", ipfsDaemonURL, &buf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create add request: %w", err)
|
||||
}
|
||||
@ -312,27 +340,53 @@ func (c *Client) AddDirectory(ctx context.Context, dirPath string) (*AddResponse
|
||||
return nil, fmt.Errorf("add failed with status %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
// Read NDJSON responses - the last one will be the root directory
|
||||
// Read NDJSON responses
|
||||
// IPFS daemon returns entries for each file and subdirectory
|
||||
// The last entry should be the root directory (or deepest subdirectory if no wrapper)
|
||||
dec := json.NewDecoder(resp.Body)
|
||||
var last AddResponse
|
||||
var rootCID string
|
||||
var lastEntry ipfsDaemonAddResponse
|
||||
|
||||
for {
|
||||
var chunk AddResponse
|
||||
var chunk ipfsDaemonAddResponse
|
||||
if err := dec.Decode(&chunk); err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
return nil, fmt.Errorf("failed to decode add response: %w", err)
|
||||
}
|
||||
last = chunk
|
||||
lastEntry = chunk
|
||||
|
||||
// With wrap-in-directory, the entry with empty name is the wrapper directory
|
||||
if chunk.Name == "" {
|
||||
rootCID = chunk.Hash
|
||||
}
|
||||
}
|
||||
|
||||
if last.Cid == "" {
|
||||
return nil, fmt.Errorf("no CID returned from IPFS")
|
||||
// Use the last entry if no wrapper directory found
|
||||
if rootCID == "" {
|
||||
rootCID = lastEntry.Hash
|
||||
}
|
||||
|
||||
if rootCID == "" {
|
||||
return nil, fmt.Errorf("no root CID returned from IPFS daemon")
|
||||
}
|
||||
|
||||
c.logger.Debug("Directory uploaded to IPFS",
|
||||
zap.String("root_cid", rootCID),
|
||||
zap.Int("file_count", fileCount),
|
||||
zap.Int64("total_size", totalSize))
|
||||
|
||||
// Pin to cluster for distribution
|
||||
_, err = c.Pin(ctx, rootCID, "", 1)
|
||||
if err != nil {
|
||||
c.logger.Warn("Failed to pin directory to cluster",
|
||||
zap.String("cid", rootCID),
|
||||
zap.Error(err))
|
||||
}
|
||||
|
||||
return &AddResponse{
|
||||
Cid: last.Cid,
|
||||
Cid: rootCID,
|
||||
Size: totalSize,
|
||||
}, nil
|
||||
}
|
||||
@ -496,8 +550,9 @@ func (c *Client) Unpin(ctx context.Context, cid string) error {
|
||||
// Get retrieves content from IPFS by CID
|
||||
// Note: This uses the IPFS HTTP API (typically on port 5001), not the Cluster API
|
||||
func (c *Client) Get(ctx context.Context, cid string, ipfsAPIURL string) (io.ReadCloser, error) {
|
||||
// Use the client's configured IPFS API URL if not provided
|
||||
if ipfsAPIURL == "" {
|
||||
ipfsAPIURL = "http://localhost:5001"
|
||||
ipfsAPIURL = c.ipfsAPIURL
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/api/v0/cat?arg=%s", ipfsAPIURL, cid)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user