From 5907450d569c997809674a2abeec97ddf0afe4aa Mon Sep 17 00:00:00 2001 From: mihna123 Date: Fri, 4 Sep 2026 01:13:17 +0200 Subject: config: add config package and wire it into server and handlers Replace the hardcoded server.Config with an ini-backed config package that reads /etc/openstore/openstore.conf then the user's config file, so user settings override system ones and both override the built-in defaults. Missing files are ignored rather than fatal. Move route registration into a handlers package that owns the templates and builds a Page view model, which removes the server -> handlers import cycle along with the setter-based context injection. --- .gitignore | 1 + cmd/openstore/main.go | 17 ++++++++- go.mod | 2 + go.sum | 20 ++++++++++ internal/config/config.go | 73 ++++++++++++++++++++++++++++++++++++ internal/server/handlers/handlers.go | 25 ++++++++++++ internal/server/handlers/pages.go | 24 ++++++++++++ internal/server/server.go | 36 +++++------------- internal/server/server_test.go | 22 ++++++++++- internal/web/templates/index.html | 6 +-- 10 files changed, 193 insertions(+), 33 deletions(-) create mode 100644 .gitignore create mode 100644 internal/config/config.go create mode 100644 internal/server/handlers/handlers.go create mode 100644 internal/server/handlers/pages.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ba2906d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +main diff --git a/cmd/openstore/main.go b/cmd/openstore/main.go index 65d7afb..48b2df0 100644 --- a/cmd/openstore/main.go +++ b/cmd/openstore/main.go @@ -4,14 +4,27 @@ import ( "log/slog" "os" + "codeberg.org/mihna123/openstore/internal/config" "codeberg.org/mihna123/openstore/internal/server" ) func main() { slog.SetDefault(slog.New(slog.NewTextHandler(os.Stdout, nil))) - conf := &server.Config{Name: "OpenStore"} + + dir, err := config.DefaultPath() + if err != nil { + slog.Error("failed to open config file", "err", err) + os.Exit(1) + } + + conf, err := config.Load(dir) + if err != nil { + slog.Error("config load failed", "err", err) + os.Exit(1) + } + s := server.New(conf) - if err := s.Serve(":3000"); err != nil { + if err := s.Serve(); err != nil { slog.Error("server failed", "err", err) os.Exit(1) } diff --git a/go.mod b/go.mod index 4c2f56c..37fa535 100644 --- a/go.mod +++ b/go.mod @@ -3,3 +3,5 @@ module codeberg.org/mihna123/openstore go 1.25.0 require golang.org/x/crypto v0.55.0 + +require gopkg.in/ini.v1 v1.67.3 diff --git a/go.sum b/go.sum index a6e2343..90c607f 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,22 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/ini.v1 v1.67.3 h1:iM9Lhz5MRSGhHVGGwCuzG9KO8PoirCXj/m/qTmOJJQw= +gopkg.in/ini.v1 v1.67.3/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..2be9dd6 --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,73 @@ +package config + +import ( + "fmt" + "net/url" + "os" + "path/filepath" + + "gopkg.in/ini.v1" +) + +type Serve struct { + Addr string `ini:"addr"` +} + +type Instance struct { + Name string `ini:"name"` + ContactEmail string `ini:"contact-email"` + BaseURL string `ini:"base-url"` +} + +type Config struct { + Serve Serve `ini:"serve"` + Instance Instance `ini:"instance"` +} + +var systemConfigPath = "/etc/openstore/openstore.conf" + +func DefaultPath() (string, error) { + dir, err := os.UserConfigDir() + if err != nil { + return "", err + } + + return filepath.Join(dir, "openstore", "openstore.conf"), nil +} + +func Load(filename string) (*Config, error) { + cfg, err := ini.LoadSources(ini.LoadOptions{ + Loose: true, + KeyValueDelimiters: "=", + }, systemConfigPath, filename) + + if err != nil { + return nil, err + } + + config := &Config{ + Serve: Serve{ + Addr: ":3000", + }, + Instance: Instance{ + Name: "Openstore", + ContactEmail: "jane@doe.com", + BaseURL: "https://www.example.com", + }, + } + + err = cfg.MapTo(config) + if err != nil { + return nil, err + } + + u, err := url.Parse(config.Instance.BaseURL) + if err != nil || u.Host == "" || u.Scheme == "" { + return nil, fmt.Errorf( + "config: base-url must be an absolute url, got %s", + config.Instance.BaseURL, + ) + } + + return config, nil +} diff --git a/internal/server/handlers/handlers.go b/internal/server/handlers/handlers.go new file mode 100644 index 0000000..958ca30 --- /dev/null +++ b/internal/server/handlers/handlers.go @@ -0,0 +1,25 @@ +package handlers + +import ( + "html/template" + "net/http" + + "codeberg.org/mihna123/openstore/internal/config" + "codeberg.org/mihna123/openstore/internal/web" +) + +type Handler struct { + tmpl *template.Template + cfg *config.Config +} + +func Routes(conf *config.Config) *http.ServeMux { + handler := Handler{ + tmpl: template.Must(template.ParseFS(web.Files, "templates/*.html")), + cfg: conf, + } + mux := http.NewServeMux() + mux.HandleFunc("GET /{$}", handler.home) + + return mux +} diff --git a/internal/server/handlers/pages.go b/internal/server/handlers/pages.go new file mode 100644 index 0000000..8c5bfb5 --- /dev/null +++ b/internal/server/handlers/pages.go @@ -0,0 +1,24 @@ +package handlers + +import ( + "log/slog" + "net/http" + + "codeberg.org/mihna123/openstore/internal/config" +) + +type Page struct { + Lang string + Instance config.Instance + Data any +} + +func (h *Handler) home(w http.ResponseWriter, r *http.Request) { + page := Page{Lang: "en", Instance: h.cfg.Instance} + err := h.tmpl.ExecuteTemplate(w, "index.html", page) + + if err != nil { + slog.Error("render template", "err", err.Error()) + return + } +} diff --git a/internal/server/server.go b/internal/server/server.go index a444958..a2f2d45 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -5,55 +5,39 @@ import ( "net" "time" - "html/template" "net/http" - "codeberg.org/mihna123/openstore/internal/web" + "codeberg.org/mihna123/openstore/internal/config" + "codeberg.org/mihna123/openstore/internal/server/handlers" ) -type Config struct { - Name string -} - type Server struct { - config *Config - tmpl *template.Template + config *config.Config mux *http.ServeMux } -func (s *Server) routes() { - s.mux.HandleFunc("GET /{$}", func(w http.ResponseWriter, r *http.Request) { - err := s.tmpl.ExecuteTemplate(w, "index.html", s.config) - - if err != nil { - slog.Error("render template", "err", err.Error()) - return - } - }) -} - -func (s *Server) Serve(addr string) error { +func (s *Server) Serve() error { httpSrv := &http.Server{ Handler: logRequests(s.mux), ReadHeaderTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second, } - ln, err := net.Listen("tcp", addr) + ln, err := net.Listen("tcp", s.config.Serve.Addr) if err != nil { return err } - slog.Info("listening", "name", s.config.Name, "addr", addr) + slog.Info("listening", "name", s.config.Instance.Name, "addr", s.config.Serve.Addr) return httpSrv.Serve(ln) } -func New(c *Config) *Server { +func New(c *config.Config) *Server { + mux := handlers.Routes(c) s := &Server{ config: c, - mux: http.NewServeMux(), - tmpl: template.Must(template.ParseFS(web.Files, "templates/*.html")), + mux: mux, } - s.routes() + return s } diff --git a/internal/server/server_test.go b/internal/server/server_test.go index 7e6a748..aea434f 100644 --- a/internal/server/server_test.go +++ b/internal/server/server_test.go @@ -7,6 +7,8 @@ import ( "os" "strings" "testing" + + "codeberg.org/mihna123/openstore/internal/config" ) func TestMain(m *testing.M) { @@ -14,9 +16,23 @@ func TestMain(m *testing.M) { os.Exit(m.Run()) } +func newTestConfig(t *testing.T) *config.Config { + t.Helper() + return &config.Config{ + Serve: config.Serve{ + Addr: ":3000", + }, + Instance: config.Instance{ + Name: "TestStore", + ContactEmail: "test@email.com", + BaseURL: "https://example.com", + }, + } +} + func newTestServer(t *testing.T) *Server { t.Helper() - return New(&Config{Name: "test"}) + return New(newTestConfig(t)) } func TestRoutes(t *testing.T) { @@ -47,7 +63,9 @@ func TestRoutes(t *testing.T) { } func TestIndexRendersTemplate(t *testing.T) { - s := New(&Config{Name: "testname123"}) + cfg := newTestConfig(t) + cfg.Instance.Name = "testname123" + s := New(cfg) rec := httptest.NewRecorder() s.mux.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/", nil)) diff --git a/internal/web/templates/index.html b/internal/web/templates/index.html index 54630e7..16ca1fe 100644 --- a/internal/web/templates/index.html +++ b/internal/web/templates/index.html @@ -3,11 +3,11 @@ - {{ .Name }} - + {{ .Instance.Name }} + -

Welcome to {{ .Name }}!

+

Welcome to {{ .Instance.Name }}!

Work in progress...

-- cgit v1.3.1