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.
137 lines
4.0 KiB
Go
137 lines
4.0 KiB
Go
package gateway
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/DeBrosOfficial/network/pkg/logging"
|
|
"github.com/DeBrosOfficial/network/pkg/rqlite"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// CreateDatabaseRequest is the request body for database creation
|
|
type CreateDatabaseRequest struct {
|
|
Database string `json:"database"`
|
|
ReplicationFactor int `json:"replication_factor,omitempty"` // defaults to 3
|
|
}
|
|
|
|
// CreateDatabaseResponse is the response for database creation
|
|
type CreateDatabaseResponse struct {
|
|
Status string `json:"status"`
|
|
Database string `json:"database"`
|
|
Message string `json:"message,omitempty"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
|
|
// databaseCreateHandler handles database creation requests via pubsub
|
|
func (g *Gateway) databaseCreateHandler(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
var req CreateDatabaseRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
g.respondJSON(w, http.StatusBadRequest, CreateDatabaseResponse{
|
|
Status: "error",
|
|
Error: "Invalid request body",
|
|
})
|
|
return
|
|
}
|
|
|
|
if req.Database == "" {
|
|
g.respondJSON(w, http.StatusBadRequest, CreateDatabaseResponse{
|
|
Status: "error",
|
|
Error: "database field is required",
|
|
})
|
|
return
|
|
}
|
|
|
|
// Default replication factor
|
|
if req.ReplicationFactor == 0 {
|
|
req.ReplicationFactor = 3
|
|
}
|
|
|
|
// Check if database already exists in metadata
|
|
if existing := g.dbMetaCache.Get(req.Database); existing != nil {
|
|
g.respondJSON(w, http.StatusConflict, CreateDatabaseResponse{
|
|
Status: "exists",
|
|
Database: req.Database,
|
|
Message: "Database already exists",
|
|
})
|
|
return
|
|
}
|
|
|
|
g.logger.ComponentInfo(logging.ComponentGeneral, "Creating database via gateway",
|
|
zap.String("database", req.Database),
|
|
zap.Int("replication_factor", req.ReplicationFactor))
|
|
|
|
// Publish DATABASE_CREATE_REQUEST via pubsub
|
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
// We need to get the node ID to act as requester
|
|
// For now, use a placeholder - the actual node will coordinate
|
|
createReq := rqlite.DatabaseCreateRequest{
|
|
DatabaseName: req.Database,
|
|
RequesterNodeID: "gateway", // Gateway is requesting on behalf of client
|
|
ReplicationFactor: req.ReplicationFactor,
|
|
}
|
|
|
|
msgData, err := rqlite.MarshalMetadataMessage(rqlite.MsgDatabaseCreateRequest, "gateway", createReq)
|
|
if err != nil {
|
|
g.logger.ComponentError(logging.ComponentGeneral, "Failed to marshal create request",
|
|
zap.Error(err))
|
|
g.respondJSON(w, http.StatusInternalServerError, CreateDatabaseResponse{
|
|
Status: "error",
|
|
Error: fmt.Sprintf("Failed to create database: %v", err),
|
|
})
|
|
return
|
|
}
|
|
|
|
// Publish to metadata topic
|
|
metadataTopic := "/debros/metadata/v1"
|
|
if err := g.client.PubSub().Publish(ctx, metadataTopic, msgData); err != nil {
|
|
g.logger.ComponentError(logging.ComponentGeneral, "Failed to publish create request",
|
|
zap.Error(err))
|
|
g.respondJSON(w, http.StatusInternalServerError, CreateDatabaseResponse{
|
|
Status: "error",
|
|
Error: fmt.Sprintf("Failed to publish create request: %v", err),
|
|
})
|
|
return
|
|
}
|
|
|
|
// Wait briefly for metadata sync (3 seconds)
|
|
waitCtx, waitCancel := context.WithTimeout(ctx, 3*time.Second)
|
|
defer waitCancel()
|
|
|
|
ticker := time.NewTicker(100 * time.Millisecond)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-waitCtx.Done():
|
|
// Timeout - database creation is async, return accepted status
|
|
g.respondJSON(w, http.StatusAccepted, CreateDatabaseResponse{
|
|
Status: "accepted",
|
|
Database: req.Database,
|
|
Message: "Database creation initiated, it may take a few seconds to become available",
|
|
})
|
|
return
|
|
case <-ticker.C:
|
|
// Check if metadata arrived
|
|
if metadata := g.dbMetaCache.Get(req.Database); metadata != nil {
|
|
g.respondJSON(w, http.StatusOK, CreateDatabaseResponse{
|
|
Status: "created",
|
|
Database: req.Database,
|
|
Message: "Database created successfully",
|
|
})
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|