147 lines
4.1 KiB
Go
147 lines
4.1 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/gorilla/mux"
|
|
"github.com/metazla/meta-core/internal/config"
|
|
"github.com/metazla/meta-core/internal/discovery"
|
|
"github.com/metazla/meta-core/internal/leader"
|
|
"github.com/metazla/meta-core/internal/storage"
|
|
)
|
|
|
|
// Server is the HTTP API server for meta-core
|
|
type Server struct {
|
|
config *config.Config
|
|
election *leader.Election
|
|
discovery *discovery.Service
|
|
storage *storage.Client
|
|
router *mux.Router
|
|
server *http.Server
|
|
}
|
|
|
|
// NewServer creates a new API server
|
|
func NewServer(
|
|
cfg *config.Config,
|
|
election *leader.Election,
|
|
disc *discovery.Service,
|
|
stor *storage.Client,
|
|
) *Server {
|
|
s := &Server{
|
|
config: cfg,
|
|
election: election,
|
|
discovery: disc,
|
|
storage: stor,
|
|
router: mux.NewRouter(),
|
|
}
|
|
|
|
s.setupRoutes()
|
|
return s
|
|
}
|
|
|
|
// setupRoutes configures all API routes
|
|
func (s *Server) setupRoutes() {
|
|
// Health and status
|
|
s.router.HandleFunc("/health", s.handleHealth).Methods("GET")
|
|
s.router.HandleFunc("/status", s.handleStatus).Methods("GET")
|
|
s.router.HandleFunc("/leader", s.handleLeader).Methods("GET")
|
|
s.router.HandleFunc("/role", s.handleRole).Methods("GET")
|
|
|
|
// Metadata operations - base endpoints
|
|
s.router.HandleFunc("/meta/{hash}", s.handleGetMeta).Methods("GET")
|
|
s.router.HandleFunc("/meta/{hash}", s.handlePutMeta).Methods("PUT")
|
|
s.router.HandleFunc("/meta/{hash}", s.handlePatchMeta).Methods("PATCH")
|
|
s.router.HandleFunc("/meta/{hash}", s.handleDeleteMeta).Methods("DELETE")
|
|
s.router.HandleFunc("/meta", s.handleListMeta).Methods("GET")
|
|
|
|
// Metadata operations - set operations (must be before property routes)
|
|
s.router.HandleFunc("/meta/{hash}/_add/{key:.*}", s.handleAddToSet).Methods("POST")
|
|
|
|
// Metadata operations - property-level (key can contain slashes)
|
|
s.router.HandleFunc("/meta/{hash}/{key:.*}", s.handleGetProperty).Methods("GET")
|
|
s.router.HandleFunc("/meta/{hash}/{key:.*}", s.handlePutProperty).Methods("PUT")
|
|
s.router.HandleFunc("/meta/{hash}/{key:.*}", s.handleDeleteProperty).Methods("DELETE")
|
|
|
|
// Data operations
|
|
s.router.HandleFunc("/data/{hash}/path", s.handleGetDataPath).Methods("GET")
|
|
s.router.HandleFunc("/data/{hash}", s.handleHeadData).Methods("HEAD")
|
|
|
|
// File operations (by CID)
|
|
s.router.HandleFunc("/file/{cid}", s.handleGetFileByCID).Methods("GET")
|
|
s.router.HandleFunc("/file/cid", s.handleComputeFileCID).Methods("POST")
|
|
|
|
// Service discovery
|
|
s.router.HandleFunc("/services", s.handleListServices).Methods("GET")
|
|
s.router.HandleFunc("/services/{name}", s.handleGetService).Methods("GET")
|
|
|
|
// Add middleware
|
|
s.router.Use(loggingMiddleware)
|
|
s.router.Use(corsMiddleware)
|
|
}
|
|
|
|
// Start starts the HTTP server
|
|
func (s *Server) Start() error {
|
|
addr := fmt.Sprintf("%s:%d", s.config.HTTPHost, s.config.HTTPPort)
|
|
|
|
s.server = &http.Server{
|
|
Addr: addr,
|
|
Handler: s.router,
|
|
ReadTimeout: 30 * time.Second,
|
|
WriteTimeout: 30 * time.Second,
|
|
IdleTimeout: 60 * time.Second,
|
|
}
|
|
|
|
log.Printf("[API] Starting HTTP server on %s", addr)
|
|
|
|
go func() {
|
|
if err := s.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
log.Printf("[API] HTTP server error: %v", err)
|
|
}
|
|
}()
|
|
|
|
return nil
|
|
}
|
|
|
|
// Stop gracefully stops the HTTP server
|
|
func (s *Server) Stop() error {
|
|
if s.server == nil {
|
|
return nil
|
|
}
|
|
|
|
log.Println("[API] Stopping HTTP server...")
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
return s.server.Shutdown(ctx)
|
|
}
|
|
|
|
// loggingMiddleware logs all requests
|
|
func loggingMiddleware(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
start := time.Now()
|
|
next.ServeHTTP(w, r)
|
|
log.Printf("[API] %s %s %s", r.Method, r.URL.Path, time.Since(start))
|
|
})
|
|
}
|
|
|
|
// corsMiddleware adds CORS headers
|
|
func corsMiddleware(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
|
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
|
|
|
|
if r.Method == "OPTIONS" {
|
|
w.WriteHeader(http.StatusOK)
|
|
return
|
|
}
|
|
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|