Compare commits
2 Commits
322d8d5c39
...
dc43bdb3af
Author | SHA1 | Date | |
---|---|---|---|
dc43bdb3af | |||
05185cb82a |
@ -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
|
||||
}
|
||||
|
@ -1,49 +0,0 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
OPTION_TYPE_STR = 0
|
||||
OPTION_TYPE_BOOL = 1
|
||||
OPTION_TYPE_URL = 2
|
||||
)
|
||||
|
||||
type Option struct {
|
||||
Name string
|
||||
Value string
|
||||
Required bool
|
||||
Type uint8
|
||||
TypeValue struct {
|
||||
URL *url.URL
|
||||
Str string
|
||||
Bool bool
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Option) Env() string {
|
||||
return strings.ToUpper(fmt.Sprintf("WEBSITE_%s", o.Name))
|
||||
}
|
||||
|
||||
func (o *Option) Load() (err error) {
|
||||
err = nil
|
||||
|
||||
switch o.Type {
|
||||
case OPTION_TYPE_STR:
|
||||
o.TypeValue.Str = o.Value
|
||||
|
||||
case OPTION_TYPE_BOOL:
|
||||
o.TypeValue.Bool = "1" == o.Value || "true" == strings.ToLower(o.Value)
|
||||
|
||||
case OPTION_TYPE_URL:
|
||||
o.TypeValue.URL, err = url.Parse(o.Value)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("invalid option type")
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
@ -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
|
||||
|
16
api/go.sum
16
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=
|
||||
|
14
api/main.go
14
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())
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><feed xmlns="http://www.w3.org/2005/Atom">
|
||||
<title>{{.app.Host}} news</title>
|
||||
<title>{{.app_url.Host}} news</title>
|
||||
<updated>{{.updated}}</updated>
|
||||
<subtitle>News and updates about my projects and self-hosted services</subtitle>
|
||||
<link href="{{.app.JoinPath "/news"}}"></link>
|
||||
<link href="{{.app_url.JoinPath "/news"}}"></link>
|
||||
{{ range .entries }}
|
||||
<entry>
|
||||
<title>{{.Title}}</title>
|
||||
|
Loading…
x
Reference in New Issue
Block a user