diff --git a/cmd/serve.go b/cmd/serve.go index 06757f7..20059d3 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -1,14 +1,31 @@ package cmd import ( + "context" + "fmt" + "os" + "os/signal" + "syscall" + "github.com/spf13/cobra" + "printer.backend/internal/server" ) var serveCmd = &cobra.Command{ Use: "serve", - Short: "Start the HTTP server", - Run: func(cmd *cobra.Command, args []string) { - // TODO: implement server + Short: "Start API and admin dashboard HTTP servers", + RunE: func(cmd *cobra.Command, args []string) error { + ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) + defer stop() + + cmd.Printf("API: http://%s\n", cfg.APIAddr()) + cmd.Printf("Admin: http://%s\n", cfg.AdminAddr()) + + if err := server.Run(ctx, cfg); err != nil { + return fmt.Errorf("server: %w", err) + } + cmd.Println("Server beendet.") + return nil }, } diff --git a/config.example.json b/config.example.json index 15fd0fc..b7abd0c 100644 --- a/config.example.json +++ b/config.example.json @@ -1,4 +1,5 @@ { "host": "127.0.0.1", - "port": 8080 + "api_port": 8080, + "admin_port": 8081 } diff --git a/internal/config/config.go b/internal/config/config.go index 2638d9e..bb5fa04 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -8,15 +8,20 @@ import ( // Config is the application configuration loaded from JSON. type Config struct { - Host string `json:"host"` - Port int `json:"port"` + Host string `json:"host"` + APIPort int `json:"api_port"` + AdminPort int `json:"admin_port"` + + // Port is deprecated; mapped to APIPort when api_port is unset. + Port int `json:"port"` } // Default returns sensible defaults when no config file is used. func Default() *Config { return &Config{ - Host: "127.0.0.1", - Port: 8080, + Host: "127.0.0.1", + APIPort: 8080, + AdminPort: 8081, } } @@ -32,5 +37,20 @@ func Load(path string) (*Config, error) { return nil, fmt.Errorf("parse config: %w", err) } + cfg.applyLegacyPort() return cfg, nil } + +func (c *Config) applyLegacyPort() { + if c.Port != 0 && c.APIPort == 8080 { + c.APIPort = c.Port + } +} + +func (c *Config) APIAddr() string { + return fmt.Sprintf("%s:%d", c.Host, c.APIPort) +} + +func (c *Config) AdminAddr() string { + return fmt.Sprintf("%s:%d", c.Host, c.AdminPort) +} diff --git a/internal/server/admin/admin.go b/internal/server/admin/admin.go new file mode 100644 index 0000000..a433db0 --- /dev/null +++ b/internal/server/admin/admin.go @@ -0,0 +1,27 @@ +package admin + +import ( + "embed" + "encoding/json" + "io/fs" + "net/http" +) + +//go:embed static +var staticFS embed.FS + +// NewHandler returns the admin dashboard HTTP handler. +func NewHandler(apiBaseURL string) http.Handler { + sub, err := fs.Sub(staticFS, "static") + if err != nil { + panic("admin static fs: " + err.Error()) + } + + mux := http.NewServeMux() + mux.HandleFunc("GET /config.json", func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(map[string]string{"api_base": apiBaseURL}) + }) + mux.Handle("/", http.FileServer(http.FS(sub))) + return mux +} diff --git a/internal/server/admin/static/index.html b/internal/server/admin/static/index.html new file mode 100644 index 0000000..8651d43 --- /dev/null +++ b/internal/server/admin/static/index.html @@ -0,0 +1,200 @@ + + +
+ + +Admin-Dashboard
++ + +
+ + +Lokale Uhrzeit (Browser)
+ +