network/pkg/gateway/gateway.go
anonpenguin23 4d05ae696b
Add admin handlers for database creation and metadata management
- 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.
2025-10-16 10:56:59 +03:00

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
}