diff --git a/api/config/config.go b/api/config/config.go index 5319703..74ea182 100644 --- a/api/config/config.go +++ b/api/config/config.go @@ -3,107 +3,47 @@ package config import ( "fmt" "net/url" - "os" + + "github.com/ngn13/ortam" ) type Type struct { - Options []Option - Count int + Debug bool // should display debug messgaes? + AppUrl *url.URL // frontend application URL for the website + Password string // admin password + Host string // host the server should listen on + IPHeader string // header that should be checked for obtaining the client IP + Interval string // service status check interval + Timeout string // timeout for the service status check + Limit string // if the service responds slower than this limit, it will be marked as "slow" } -func (c *Type) Find(name string, typ uint8) (*Option, error) { - for i := 0; i < c.Count; i++ { - if c.Options[i].Name != name { - continue - } - - if c.Options[i].Type != typ { - return nil, fmt.Errorf("bad option type") - } - - return &c.Options[i], nil +func Load() (*Type, error) { + var conf = Type{ + Debug: false, + Password: "", + Host: "0.0.0.0:7002", + IPHeader: "X-Real-IP", + Interval: "1h", + Timeout: "15s", + Limit: "5s", } - return nil, fmt.Errorf("option not found") -} - -func (c *Type) Load() (err error) { - var ( - env_val string - env_name string - opt *Option - exists bool - ) - - // default options - c.Options = []Option{ - {Name: "debug", Value: "false", Type: OPTION_TYPE_BOOL, Required: true}, // should display debug messgaes? - {Name: "app_url", Value: "http://localhost:7001/", Type: OPTION_TYPE_URL, Required: true}, // frontend application URL for the website - {Name: "password", Value: "", Type: OPTION_TYPE_STR, Required: true}, // admin password - {Name: "host", Value: "0.0.0.0:7002", Type: OPTION_TYPE_STR, Required: true}, // host the server should listen on - {Name: "ip_header", Value: "X-Real-IP", Type: OPTION_TYPE_STR, Required: false}, // header that should be checked for obtaining the client IP - {Name: "interval", Value: "1h", Type: OPTION_TYPE_STR, Required: false}, // service status check interval - {Name: "timeout", Value: "15s", Type: OPTION_TYPE_STR, Required: false}, // timeout for the service status check - {Name: "limit", Value: "5s", Type: OPTION_TYPE_STR, Required: false}, // if the service responds slower than this limit, it will be marked as "slow" - } - c.Count = len(c.Options) - - for i := 0; i < c.Count; i++ { - opt = &c.Options[i] - - env_name = opt.Env() - - if env_val, exists = os.LookupEnv(env_name); exists { - opt.Value = env_val - } - - if opt.Value == "" && opt.Required { - return fmt.Errorf("please specify a value for the config option \"%s\" (\"%s\")", opt.Name, env_name) - } - - if err = opt.Load(); err != nil { - return fmt.Errorf("failed to load option \"%s\" (\"%s\"): %s", opt.Name, env_name, err.Error()) - } - } - - return nil -} - -func (c *Type) GetStr(name string) string { - var ( - opt *Option - err error - ) - - if opt, err = c.Find(name, OPTION_TYPE_STR); err != nil { - return "" - } - - return opt.TypeValue.Str -} - -func (c *Type) GetBool(name string) bool { - var ( - opt *Option - err error - ) - - if opt, err = c.Find(name, OPTION_TYPE_BOOL); err != nil { - return false - } - - return opt.TypeValue.Bool -} - -func (c *Type) GetURL(name string) *url.URL { - var ( - opt *Option - err error - ) - - if opt, err = c.Find(name, OPTION_TYPE_URL); err != nil { - return nil - } - - return opt.TypeValue.URL + if err := ortam.Load(&conf, "WEBSITE"); err != nil { + return nil, err + } + + if conf.AppUrl == nil { + conf.AppUrl, _ = url.Parse("http://localhost:7001/") + } + + if conf.Password == "" { + return nil, fmt.Errorf("password is not specified") + } + + if conf.Host == "" { + return nil, fmt.Errorf("host address is not specified") + } + + return &conf, nil } diff --git a/api/go.mod b/api/go.mod index 9912cdb..ea75791 100644 --- a/api/go.mod +++ b/api/go.mod @@ -1,6 +1,6 @@ module github.com/ngn13/website/api -go 1.21.3 +go 1.24.0 require ( github.com/gofiber/fiber/v2 v2.52.6 @@ -14,6 +14,7 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/ngn13/ortam v0.0.0-20250412195317-e76e62a7a305 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.51.0 // indirect diff --git a/api/go.sum b/api/go.sum index 403a8cd..8828bc8 100644 --- a/api/go.sum +++ b/api/go.sum @@ -1,17 +1,9 @@ -github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= -github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= -github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo= -github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI= github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= -github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= -github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= -github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -19,14 +11,12 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= -github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= -github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.27 h1:drZCnuvf37yPfs95E5jd9s3XhdVWLal+6BOK6qrv6IU= github.com/mattn/go-sqlite3 v1.14.27/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/ngn13/ortam v0.0.0-20250412195317-e76e62a7a305 h1:1YxtSMwR14PklXNlZxIqcmfpiq2+G98YNmhSuz7GKCQ= +github.com/ngn13/ortam v0.0.0-20250412195317-e76e62a7a305/go.mod h1:MSJZ4ZstrLvVEvivbp9hhup+iL8rvtpgKcYaF3DSOKk= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= @@ -37,7 +27,5 @@ github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVS github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/api/main.go b/api/main.go index 0d4af96..9d2f71e 100644 --- a/api/main.go +++ b/api/main.go @@ -36,18 +36,18 @@ func main() { app *fiber.App stat status.Type - conf config.Type + conf *config.Type db database.Type err error ) - if err = conf.Load(); err != nil { + if conf, err = config.Load(); err != nil { util.Fail("failed to load the configuration: %s", err.Error()) return } - if !conf.GetBool("debug") { + if !conf.Debug { util.Debg = func(m string, v ...any) {} } @@ -56,7 +56,7 @@ func main() { return } - if err = stat.Setup(&conf, &db); err != nil { + if err = stat.Setup(conf, &db); err != nil { util.Fail("failed to setup the status checker: %s", err.Error()) return } @@ -75,7 +75,7 @@ func main() { c.Set("Access-Control-Allow-Methods", "PUT, DELETE, GET") // POST can be sent from HTML forms, so I prefer PUT for API endpoints c.Locals("status", &stat) - c.Locals("config", &conf) + c.Locals("config", conf) c.Locals("database", &db) return c.Next() @@ -121,9 +121,9 @@ func main() { } // start the app - util.Info("starting web server on %s", conf.GetStr("host")) + util.Info("starting web server on %s", conf.Host) - if err = app.Listen(conf.GetStr("host")); err != nil { + if err = app.Listen(conf.Host); err != nil { util.Fail("failed to start the web server: %s", err.Error()) } diff --git a/api/routes/admin.go b/api/routes/admin.go index 142b516..760bca1 100644 --- a/api/routes/admin.go +++ b/api/routes/admin.go @@ -21,7 +21,7 @@ func admin_log(c *fiber.Ctx, m string) error { func AuthMiddleware(c *fiber.Ctx) error { conf := c.Locals("config").(*config.Type) - if c.Get("Authorization") != conf.GetStr("password") { + if c.Get("Authorization") != conf.Password { return util.ErrAuth(c) } diff --git a/api/routes/index.go b/api/routes/index.go index 1d60bc7..244546c 100644 --- a/api/routes/index.go +++ b/api/routes/index.go @@ -7,8 +7,7 @@ import ( func GET_Index(c *fiber.Ctx) error { conf := c.Locals("config").(*config.Type) - app := conf.GetURL("app_url") // redirect to the API documentation - return c.Redirect(app.JoinPath("/doc/api").String()) + return c.Redirect(conf.AppUrl.JoinPath("/doc/api").String()) } diff --git a/api/routes/news.go b/api/routes/news.go index 1e8cdca..badd134 100644 --- a/api/routes/news.go +++ b/api/routes/news.go @@ -40,7 +40,6 @@ func GET_News(c *fiber.Ctx) error { db := c.Locals("database").(*database.Type) conf := c.Locals("config").(*config.Type) - app := conf.GetURL("app_url") lang := c.Params("lang") if lang == "" || len(lang) != 2 { @@ -63,10 +62,10 @@ func GET_News(c *fiber.Ctx) error { }) if feed, err = util.Render("views/news.xml", fiber.Map{ + "app_url": conf.AppUrl, "updated": time.Now().Format(time.RFC3339), "entries": entries, "lang": lang, - "app": app, }); err != nil { return util.ErrInternal(c, err) } diff --git a/api/status/status.go b/api/status/status.go index 8c2a74f..264750e 100644 --- a/api/status/status.go +++ b/api/status/status.go @@ -66,29 +66,24 @@ func (s *Type) loop() { func (s *Type) Setup(conf *config.Type, db *database.Type) error { var ( - dur time.Duration - iv, to, lm string - err error + dur time.Duration + err error ) - iv = conf.GetStr("interval") - to = conf.GetStr("timeout") - lm = conf.GetStr("limit") - - if iv == "" || to == "" || lm == "" { + if conf.Interval == "" || conf.Timeout == "" || conf.Limit == "" { s.disabled = true return nil } - if dur, err = util.GetDuration(iv); err != nil { + if dur, err = util.GetDuration(conf.Interval); err != nil { return err } - if s.timeout, err = util.GetDuration(iv); err != nil { + if s.timeout, err = util.GetDuration(conf.Timeout); err != nil { return err } - if s.limit, err = util.GetDuration(iv); err != nil { + if s.limit, err = util.GetDuration(conf.Limit); err != nil { return err } diff --git a/api/util/res.go b/api/util/res.go index d9c63dd..7f429e8 100644 --- a/api/util/res.go +++ b/api/util/res.go @@ -10,10 +10,9 @@ import ( func IP(c *fiber.Ctx) string { conf := c.Locals("config").(*config.Type) - ip_header := conf.GetStr("ip_header") - if ip_header != "" && c.Get(ip_header) != "" { - return strings.Clone(c.Get(ip_header)) + if conf.IPHeader != "" && c.Get(conf.IPHeader) != "" { + return strings.Clone(c.Get(conf.IPHeader)) } return c.IP() diff --git a/api/views/news.xml b/api/views/news.xml index a5a0a28..ef23031 100644 --- a/api/views/news.xml +++ b/api/views/news.xml @@ -1,8 +1,8 @@ - {{.app.Host}} news + {{.app_url.Host}} news {{.updated}} News and updates about my projects and self-hosted services - + {{ range .entries }} {{.Title}}