mirror of
https://github.com/DeBrosOfficial/network.git
synced 2025-12-15 05:08:49 +00:00
- Introduced `admin_handlers.go` to handle database creation requests via HTTP, including validation and response handling. - Implemented `db_metadata.go` to manage database metadata caching and synchronization with a pubsub subscriber. - Updated `gateway.go` to initialize the metadata cache and start the metadata subscriber in the background. - Added new route for database creation in `routes.go` to expose the new functionality. - Enhanced cluster management to support system database auto-joining and improved metadata handling for database operations.
173 lines
5.6 KiB
Go
173 lines
5.6 KiB
Go
package gateway
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/DeBrosOfficial/network/pkg/client"
|
|
"github.com/DeBrosOfficial/network/pkg/logging"
|
|
"github.com/multiformats/go-multiaddr"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// Config holds configuration for the gateway server
|
|
type Config struct {
|
|
ListenAddr string
|
|
ClientNamespace string
|
|
BootstrapPeers []string
|
|
}
|
|
|
|
type Gateway struct {
|
|
logger *logging.ColoredLogger
|
|
cfg *Config
|
|
client client.NetworkClient
|
|
startedAt time.Time
|
|
signingKey *rsa.PrivateKey
|
|
keyID string
|
|
dbMetaCache *DatabaseMetadataCache
|
|
}
|
|
|
|
// deriveRQLiteEndpoints extracts IP addresses from bootstrap peer multiaddrs
|
|
// and constructs RQLite HTTP endpoints using the fixed system database port (5001)
|
|
func deriveRQLiteEndpoints(bootstrapPeers []string, systemHTTPPort int) []string {
|
|
if systemHTTPPort == 0 {
|
|
systemHTTPPort = 5001 // default
|
|
}
|
|
|
|
endpoints := make([]string, 0)
|
|
seen := make(map[string]bool)
|
|
|
|
for _, peerAddr := range bootstrapPeers {
|
|
ma, err := multiaddr.NewMultiaddr(peerAddr)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
// Extract IP address from multiaddr
|
|
var ip string
|
|
multiaddr.ForEach(ma, func(c multiaddr.Component) bool {
|
|
if c.Protocol().Code == multiaddr.P_IP4 {
|
|
ip = c.Value()
|
|
return false // stop iteration
|
|
}
|
|
if c.Protocol().Code == multiaddr.P_IP6 {
|
|
ip = "[" + c.Value() + "]" // IPv6 needs brackets
|
|
return false
|
|
}
|
|
return true
|
|
})
|
|
|
|
if ip != "" && !seen[ip] {
|
|
endpoint := fmt.Sprintf("http://%s:%d", ip, systemHTTPPort)
|
|
endpoints = append(endpoints, endpoint)
|
|
seen[ip] = true
|
|
}
|
|
}
|
|
|
|
return endpoints
|
|
}
|
|
|
|
// New creates and initializes a new Gateway instance
|
|
func New(logger *logging.ColoredLogger, cfg *Config) (*Gateway, error) {
|
|
logger.ComponentInfo(logging.ComponentGeneral, "Building client config...")
|
|
|
|
// Build client config from gateway cfg
|
|
// Gateway uses the system database for API keys, wallets, etc.
|
|
cliCfg := client.DefaultClientConfig("_system")
|
|
cliCfg.DatabaseName = "_system" // Override to use system database directly
|
|
if len(cfg.BootstrapPeers) > 0 {
|
|
cliCfg.BootstrapPeers = cfg.BootstrapPeers
|
|
}
|
|
|
|
// Derive RQLite endpoints from bootstrap peers
|
|
// Check for env override first
|
|
if envDSN := strings.TrimSpace(os.Getenv("GATEWAY_RQLITE_DSN")); envDSN != "" {
|
|
cliCfg.DatabaseEndpoints = strings.Split(envDSN, ",")
|
|
for i, ep := range cliCfg.DatabaseEndpoints {
|
|
cliCfg.DatabaseEndpoints[i] = strings.TrimSpace(ep)
|
|
}
|
|
logger.ComponentInfo(logging.ComponentGeneral, "Using RQLite endpoints from GATEWAY_RQLITE_DSN env",
|
|
zap.Strings("endpoints", cliCfg.DatabaseEndpoints))
|
|
} else {
|
|
// Auto-derive from bootstrap peers + system port (5001)
|
|
// This will try port 5001 on each peer (works for single-node and distributed clusters)
|
|
// For multi-node localhost, set GATEWAY_RQLITE_DSN to the actual ports
|
|
cliCfg.DatabaseEndpoints = deriveRQLiteEndpoints(cfg.BootstrapPeers, 5001)
|
|
logger.ComponentInfo(logging.ComponentGeneral, "Derived RQLite endpoints from bootstrap peers",
|
|
zap.Strings("endpoints", cliCfg.DatabaseEndpoints),
|
|
zap.String("note", "For multi-node localhost, set GATEWAY_RQLITE_DSN env to actual ports"))
|
|
}
|
|
|
|
logger.ComponentInfo(logging.ComponentGeneral, "Creating network client...")
|
|
c, err := client.NewClient(cliCfg)
|
|
if err != nil {
|
|
logger.ComponentError(logging.ComponentClient, "failed to create network client", zap.Error(err))
|
|
return nil, err
|
|
}
|
|
|
|
logger.ComponentInfo(logging.ComponentGeneral, "Connecting network client...")
|
|
if err := c.Connect(); err != nil {
|
|
logger.ComponentError(logging.ComponentClient, "failed to connect network client", zap.Error(err))
|
|
return nil, err
|
|
}
|
|
|
|
logger.ComponentInfo(logging.ComponentClient, "Network client connected",
|
|
zap.String("namespace", cliCfg.AppName),
|
|
zap.Int("bootstrap_peer_count", len(cliCfg.BootstrapPeers)),
|
|
)
|
|
|
|
logger.ComponentInfo(logging.ComponentGeneral, "Creating gateway instance...")
|
|
gw := &Gateway{
|
|
logger: logger,
|
|
cfg: cfg,
|
|
client: c,
|
|
startedAt: time.Now(),
|
|
dbMetaCache: NewDatabaseMetadataCache(logger),
|
|
}
|
|
|
|
logger.ComponentInfo(logging.ComponentGeneral, "Starting metadata subscriber...")
|
|
// Start metadata subscriber in background
|
|
go func() {
|
|
ctx := context.Background()
|
|
if err := gw.StartMetadataSubscriber(ctx); err != nil {
|
|
logger.ComponentWarn(logging.ComponentGeneral, "failed to start metadata subscriber", zap.Error(err))
|
|
}
|
|
}()
|
|
|
|
logger.ComponentInfo(logging.ComponentGeneral, "Generating RSA signing key...")
|
|
// Generate local RSA signing key for JWKS/JWT (ephemeral for now)
|
|
if key, err := rsa.GenerateKey(rand.Reader, 2048); err == nil {
|
|
gw.signingKey = key
|
|
gw.keyID = "gw-" + strconv.FormatInt(time.Now().Unix(), 10)
|
|
logger.ComponentInfo(logging.ComponentGeneral, "RSA key generated successfully")
|
|
} else {
|
|
logger.ComponentWarn(logging.ComponentGeneral, "failed to generate RSA key; jwks will be empty", zap.Error(err))
|
|
}
|
|
|
|
logger.ComponentInfo(logging.ComponentGeneral, "Gateway initialized with dynamic database clustering")
|
|
|
|
logger.ComponentInfo(logging.ComponentGeneral, "Gateway creation completed, returning...")
|
|
return gw, nil
|
|
}
|
|
|
|
// withInternalAuth creates a context for internal gateway operations that bypass authentication
|
|
func (g *Gateway) withInternalAuth(ctx context.Context) context.Context {
|
|
return client.WithInternalAuth(ctx)
|
|
}
|
|
|
|
// Close disconnects the gateway client
|
|
func (g *Gateway) Close() {
|
|
if g.client != nil {
|
|
if err := g.client.Disconnect(); err != nil {
|
|
g.logger.ComponentWarn(logging.ComponentClient, "error during client disconnect", zap.Error(err))
|
|
}
|
|
}
|
|
// No legacy database connections to close
|
|
}
|