mirror of
https://github.com/jwetzell/showbridge-go.git
synced 2026-04-26 21:05:30 +00:00
256 lines
7.6 KiB
Go
256 lines
7.6 KiB
Go
package showbridge
|
|
|
|
import (
|
|
"context"
|
|
_ "embed"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/jwetzell/showbridge-go/internal/config"
|
|
"github.com/jwetzell/showbridge-go/internal/module"
|
|
"github.com/jwetzell/showbridge-go/internal/route"
|
|
"github.com/jwetzell/showbridge-go/internal/schema"
|
|
)
|
|
|
|
func (r *Router) startAPIServer(config config.ApiConfig) {
|
|
if !config.Enabled {
|
|
r.logger.Warn("API not enabled")
|
|
return
|
|
}
|
|
r.logger.Debug("starting API server", "port", config.Port)
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("/ws", r.handleWebsocket)
|
|
mux.HandleFunc("/health", r.handleHealthHTTP)
|
|
mux.HandleFunc("/api/v1/config", r.handleConfigHTTP)
|
|
mux.HandleFunc("/schema/config.schema.json", handleConfigSchema)
|
|
mux.HandleFunc("/schema/routes.schema.json", handleRoutesSchema)
|
|
mux.HandleFunc("/schema/modules.schema.json", handleModulesSchema)
|
|
mux.HandleFunc("/schema/processors.schema.json", handleProcessorsSchema)
|
|
|
|
r.apiServerMu.Lock()
|
|
defer r.apiServerMu.Unlock()
|
|
r.apiServer = &http.Server{
|
|
Addr: fmt.Sprintf(":%d", config.Port),
|
|
Handler: mux,
|
|
}
|
|
|
|
go func() {
|
|
r.apiServer.ListenAndServe()
|
|
r.apiServerShutdown()
|
|
}()
|
|
}
|
|
|
|
func (r *Router) stopAPIServer() {
|
|
if r.apiServer == nil {
|
|
return
|
|
}
|
|
r.logger.Debug("stopping API server")
|
|
r.apiServerMu.Lock()
|
|
defer r.apiServerMu.Unlock()
|
|
if r.apiServer != nil {
|
|
apiShutdownCtx, apiShutdownCancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
r.apiServerShutdown = apiShutdownCancel
|
|
r.apiServer.Shutdown(apiShutdownCtx)
|
|
<-apiShutdownCtx.Done()
|
|
r.apiServer = nil
|
|
}
|
|
}
|
|
|
|
func (r *Router) handleHealthHTTP(w http.ResponseWriter, req *http.Request) {
|
|
switch req.Method {
|
|
case http.MethodGet:
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
w.WriteHeader(http.StatusOK)
|
|
case http.MethodOptions:
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
|
|
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
|
|
w.WriteHeader(http.StatusOK)
|
|
default:
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
}
|
|
}
|
|
|
|
func (r *Router) handleConfigHTTP(w http.ResponseWriter, req *http.Request) {
|
|
|
|
switch req.Method {
|
|
case http.MethodGet:
|
|
configJSON, err := json.Marshal(r.runningConfig)
|
|
if err != nil {
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Write(configJSON)
|
|
case http.MethodPut:
|
|
if r.updatingConfig {
|
|
http.Error(w, "Config update in progress.", http.StatusConflict)
|
|
return
|
|
}
|
|
//TODO(jwetzell): again way too much marshaling
|
|
cfgBytes, err := io.ReadAll(req.Body)
|
|
if err != nil {
|
|
http.Error(w, "Bad request", http.StatusBadRequest)
|
|
return
|
|
}
|
|
cfgMap := make(map[string]any)
|
|
err = json.Unmarshal(cfgBytes, &cfgMap)
|
|
if err != nil {
|
|
http.Error(w, "Bad request", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
err = schema.ApplyDefaults(&cfgMap)
|
|
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
err = schema.ValidateConfig(cfgMap)
|
|
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
validCfgBytes, err := json.Marshal(cfgMap)
|
|
if err != nil {
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
var newConfig config.Config
|
|
err = json.Unmarshal(validCfgBytes, &newConfig)
|
|
if err != nil {
|
|
http.Error(w, "Bad request", http.StatusBadRequest)
|
|
return
|
|
}
|
|
moduleErrors, routeErrors := r.UpdateConfig(newConfig)
|
|
if len(moduleErrors) > 0 || len(routeErrors) > 0 {
|
|
errorResponse := struct {
|
|
ModuleErrors []module.ModuleError `json:"moduleErrors,omitempty"`
|
|
RouteErrors []route.RouteError `json:"routeErrors,omitempty"`
|
|
}{
|
|
ModuleErrors: moduleErrors,
|
|
RouteErrors: routeErrors,
|
|
}
|
|
errorResponseJSON, err := json.Marshal(errorResponse)
|
|
if err != nil {
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
w.Write(errorResponseJSON)
|
|
return
|
|
}
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
w.WriteHeader(http.StatusOK)
|
|
r.ConfigChange <- newConfig
|
|
case http.MethodOptions:
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
w.Header().Set("Access-Control-Allow-Methods", "GET, PUT, OPTIONS")
|
|
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
|
|
w.WriteHeader(http.StatusOK)
|
|
default:
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
}
|
|
}
|
|
|
|
func handleConfigSchema(w http.ResponseWriter, req *http.Request) {
|
|
switch req.Method {
|
|
case http.MethodGet:
|
|
schemaJSON, err := json.Marshal(schema.ConfigSchema)
|
|
if err != nil {
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Write(schemaJSON)
|
|
case http.MethodOptions:
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
|
|
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
|
|
w.WriteHeader(http.StatusOK)
|
|
default:
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
}
|
|
}
|
|
|
|
func handleRoutesSchema(w http.ResponseWriter, req *http.Request) {
|
|
switch req.Method {
|
|
case http.MethodGet:
|
|
schemaJSON, err := json.Marshal(schema.RoutesConfigSchema)
|
|
if err != nil {
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Write(schemaJSON)
|
|
case http.MethodOptions:
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
|
|
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
|
|
w.WriteHeader(http.StatusOK)
|
|
default:
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
}
|
|
}
|
|
|
|
func handleModulesSchema(w http.ResponseWriter, req *http.Request) {
|
|
switch req.Method {
|
|
case http.MethodGet:
|
|
schemaJSON, err := json.Marshal(schema.GetModulesSchema())
|
|
if err != nil {
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Write(schemaJSON)
|
|
case http.MethodOptions:
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
|
|
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
|
|
w.WriteHeader(http.StatusOK)
|
|
default:
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
}
|
|
}
|
|
|
|
func handleProcessorsSchema(w http.ResponseWriter, req *http.Request) {
|
|
switch req.Method {
|
|
case http.MethodGet:
|
|
schemaJSON, err := json.Marshal(schema.GetProcessorsSchema())
|
|
if err != nil {
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Write(schemaJSON)
|
|
case http.MethodOptions:
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
|
|
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
|
|
w.WriteHeader(http.StatusOK)
|
|
default:
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
}
|
|
}
|