diff --git a/backend/app/api/app.go b/backend/app/api/app.go
index 73d7809..5d285d3 100644
--- a/backend/app/api/app.go
+++ b/backend/app/api/app.go
@@ -1,22 +1,18 @@
package main
import (
- "time"
-
"github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/core/services/reporting/eventbus"
"github.com/hay-kot/homebox/backend/internal/data/ent"
"github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/sys/config"
"github.com/hay-kot/homebox/backend/pkgs/mailer"
- "github.com/hay-kot/httpkit/server"
)
type app struct {
conf *config.Config
mailer mailer.Mailer
db *ent.Client
- server *server.Server
repos *repo.AllRepos
services *services.AllServices
bus *eventbus.EventBus
@@ -37,13 +33,3 @@ func new(conf *config.Config) *app {
return s
}
-
-func (a *app) startBgTask(t time.Duration, fn func()) {
- timer := time.NewTimer(t)
-
- for {
- timer.Reset(t)
- a.server.Background(fn)
- <-timer.C
- }
-}
diff --git a/backend/app/api/bgrunner.go b/backend/app/api/bgrunner.go
new file mode 100644
index 0000000..ce4b7cc
--- /dev/null
+++ b/backend/app/api/bgrunner.go
@@ -0,0 +1,37 @@
+package main
+
+import (
+ "context"
+ "time"
+)
+
+type BackgroundTask struct {
+ name string
+ Interval time.Duration
+ Fn func(context.Context)
+}
+
+func (tsk *BackgroundTask) Name() string {
+ return tsk.name
+}
+
+func NewTask(name string, interval time.Duration, fn func(context.Context)) *BackgroundTask {
+ return &BackgroundTask{
+ Interval: interval,
+ Fn: fn,
+ }
+}
+
+func (tsk *BackgroundTask) Start(ctx context.Context) error {
+ timer := time.NewTimer(tsk.Interval)
+
+ for {
+ select {
+ case <-ctx.Done():
+ return nil
+ case <-timer.C:
+ timer.Reset(tsk.Interval)
+ tsk.Fn(ctx)
+ }
+ }
+}
diff --git a/backend/app/api/main.go b/backend/app/api/main.go
index 1389285..0a352e9 100644
--- a/backend/app/api/main.go
+++ b/backend/app/api/main.go
@@ -23,7 +23,7 @@ import (
"github.com/hay-kot/homebox/backend/internal/sys/config"
"github.com/hay-kot/homebox/backend/internal/web/mid"
"github.com/hay-kot/httpkit/errchain"
- "github.com/hay-kot/httpkit/server"
+ "github.com/hay-kot/httpkit/graceful"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/rs/zerolog/pkgerrors"
@@ -178,41 +178,54 @@ func run(cfg *config.Config) error {
middleware.StripSlashes,
)
- chain := errchain.New(mid.Errors(app.server, logger))
+ chain := errchain.New(mid.Errors(logger))
app.mountRoutes(router, chain, app.repos)
- app.server = server.NewServer(
- server.WithHost(app.conf.Web.Host),
- server.WithPort(app.conf.Web.Port),
- server.WithReadTimeout(app.conf.Web.ReadTimeout),
- server.WithWriteTimeout(app.conf.Web.WriteTimeout),
- server.WithIdleTimeout(app.conf.Web.IdleTimeout),
- )
- log.Info().Msgf("Starting HTTP Server on %s:%s", app.server.Host, app.server.Port)
+ runner := graceful.NewRunner()
+
+ runner.AddFunc("server", func(ctx context.Context) error {
+ httpserver := http.Server{
+ Addr: fmt.Sprintf("%s:%s", cfg.Web.Host, cfg.Web.Port),
+ Handler: router,
+ ReadTimeout: cfg.Web.ReadTimeout,
+ WriteTimeout: cfg.Web.WriteTimeout,
+ IdleTimeout: cfg.Web.IdleTimeout,
+ }
+
+ go func() {
+ <-ctx.Done()
+ _ = httpserver.Shutdown(context.Background())
+ }()
+
+ log.Info().Msgf("Server is running on %s:%s", cfg.Web.Host, cfg.Web.Port)
+ return httpserver.ListenAndServe()
+ })
// =========================================================================
// Start Reoccurring Tasks
- go app.bus.Run()
+ runner.AddFunc("eventbus", app.bus.Run)
- go app.startBgTask(time.Duration(24)*time.Hour, func() {
- _, err := app.repos.AuthTokens.PurgeExpiredTokens(context.Background())
+ runner.AddPlugin(NewTask("purge-tokens", time.Duration(24)*time.Hour, func(ctx context.Context) {
+ _, err := app.repos.AuthTokens.PurgeExpiredTokens(ctx)
if err != nil {
log.Error().
Err(err).
Msg("failed to purge expired tokens")
}
- })
- go app.startBgTask(time.Duration(24)*time.Hour, func() {
- _, err := app.repos.Groups.InvitationPurge(context.Background())
+ }))
+
+ runner.AddPlugin(NewTask("purge-invitations", time.Duration(24)*time.Hour, func(ctx context.Context) {
+ _, err := app.repos.Groups.InvitationPurge(ctx)
if err != nil {
log.Error().
Err(err).
Msg("failed to purge expired invitations")
}
- })
- go app.startBgTask(time.Duration(1)*time.Hour, func() {
+ }))
+
+ runner.AddPlugin(NewTask("send-notifications", time.Duration(1)*time.Hour, func(ctx context.Context) {
now := time.Now()
if now.Hour() == 8 {
@@ -224,7 +237,7 @@ func run(cfg *config.Config) error {
Msg("failed to send notifiers")
}
}
- })
+ }))
// TODO: Remove through external API that does setup
if cfg.Demo {
@@ -233,13 +246,24 @@ func run(cfg *config.Config) error {
}
if cfg.Debug.Enabled {
- debugrouter := app.debugRouter()
- go func() {
- if err := http.ListenAndServe(":"+cfg.Debug.Port, debugrouter); err != nil {
- log.Fatal().Err(err).Msg("failed to start debug server")
+ runner.AddFunc("debug", func(ctx context.Context) error {
+ debugserver := http.Server{
+ Addr: fmt.Sprintf("%s:%s", cfg.Web.Host, cfg.Debug.Port),
+ Handler: app.debugRouter(),
+ ReadTimeout: cfg.Web.ReadTimeout,
+ WriteTimeout: cfg.Web.WriteTimeout,
+ IdleTimeout: cfg.Web.IdleTimeout,
}
- }()
+
+ go func() {
+ <-ctx.Done()
+ _ = debugserver.Shutdown(context.Background())
+ }()
+
+ log.Info().Msgf("Debug server is running on %s:%s", cfg.Web.Host, cfg.Debug.Port)
+ return debugserver.ListenAndServe()
+ })
}
- return app.server.Start(router)
+ return runner.Start(context.Background())
}
diff --git a/backend/go.mod b/backend/go.mod
index 1b9bfea..8a0f9d9 100644
--- a/backend/go.mod
+++ b/backend/go.mod
@@ -14,7 +14,7 @@ require (
github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a
github.com/google/uuid v1.6.0
github.com/gorilla/schema v1.2.1
- github.com/hay-kot/httpkit v0.0.6
+ github.com/hay-kot/httpkit v0.0.9
github.com/mattn/go-sqlite3 v1.14.22
github.com/olahol/melody v1.1.4
github.com/pkg/errors v0.9.1
diff --git a/backend/go.sum b/backend/go.sum
index 755099b..fec9706 100644
--- a/backend/go.sum
+++ b/backend/go.sum
@@ -86,6 +86,12 @@ github.com/hashicorp/hcl/v2 v2.19.1 h1://i05Jqznmb2EXqa39Nsvyan2o5XyMowW5fnCKW5R
github.com/hashicorp/hcl/v2 v2.19.1/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE=
github.com/hay-kot/httpkit v0.0.6 h1:BidC4UrkS7zRhoTdpKLeF8ODJPKcOZkJ2tk2t2ZIQjQ=
github.com/hay-kot/httpkit v0.0.6/go.mod h1:1s/OJwWRyH6tBtTw76jTp6kwBYvjswziXaokPQH7eKQ=
+github.com/hay-kot/httpkit v0.0.7 h1:KxGi+MwXFavfFUfJEMpye5cnMef9TlFu3v7UZipUB8U=
+github.com/hay-kot/httpkit v0.0.7/go.mod h1:AD22YluZrvBDxmtB3Pw2SOyp3A2PZqcmBZa0+COrhoU=
+github.com/hay-kot/httpkit v0.0.8 h1:n+Z5z35YZcdD9cGwbnIPRbrgDw9LY6lqakH4zYr5z+A=
+github.com/hay-kot/httpkit v0.0.8/go.mod h1:AD22YluZrvBDxmtB3Pw2SOyp3A2PZqcmBZa0+COrhoU=
+github.com/hay-kot/httpkit v0.0.9 h1:hu2TPY9awmIYWXxWGubaXl2U61pPvaVsm9YwboBRGu0=
+github.com/hay-kot/httpkit v0.0.9/go.mod h1:AD22YluZrvBDxmtB3Pw2SOyp3A2PZqcmBZa0+COrhoU=
github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc=
github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
diff --git a/backend/internal/core/services/reporting/eventbus/eventbus.go b/backend/internal/core/services/reporting/eventbus/eventbus.go
index e221b44..cf606fe 100644
--- a/backend/internal/core/services/reporting/eventbus/eventbus.go
+++ b/backend/internal/core/services/reporting/eventbus/eventbus.go
@@ -2,6 +2,7 @@
package eventbus
import (
+ "context"
"sync"
"github.com/google/uuid"
@@ -43,24 +44,29 @@ func New() *EventBus {
}
}
-func (e *EventBus) Run() {
+func (e *EventBus) Run(ctx context.Context) error {
if e.started {
panic("event bus already started")
}
e.started = true
- for event := range e.ch {
- e.mu.RLock()
- arr, ok := e.subscribers[event.event]
- e.mu.RUnlock()
+ for {
+ select {
+ case <-ctx.Done():
+ return nil
+ case event := <-e.ch:
+ e.mu.RLock()
+ arr, ok := e.subscribers[event.event]
+ e.mu.RUnlock()
- if !ok {
- continue
- }
+ if !ok {
+ continue
+ }
- for _, fn := range arr {
- fn(event.data)
+ for _, fn := range arr {
+ fn(event.data)
+ }
}
}
}
diff --git a/backend/internal/data/repo/main_test.go b/backend/internal/data/repo/main_test.go
index ad5fcb9..47e5ec0 100644
--- a/backend/internal/data/repo/main_test.go
+++ b/backend/internal/data/repo/main_test.go
@@ -45,7 +45,9 @@ func TestMain(m *testing.M) {
log.Fatalf("failed opening connection to sqlite: %v", err)
}
- go tbus.Run()
+ go func() {
+ _ = tbus.Run(context.Background())
+ }()
err = client.Schema.Create(context.Background())
if err != nil {
diff --git a/backend/internal/sys/config/conf.go b/backend/internal/sys/config/conf.go
index 3b84e7d..8b7b23c 100644
--- a/backend/internal/sys/config/conf.go
+++ b/backend/internal/sys/config/conf.go
@@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"os"
+ "time"
"github.com/ardanlabs/conf/v3"
)
@@ -39,12 +40,12 @@ type DebugConf struct {
}
type WebConfig struct {
- Port string `yaml:"port" conf:"default:7745"`
- Host string `yaml:"host"`
- MaxUploadSize int64 `yaml:"max_file_upload" conf:"default:10"`
- ReadTimeout int `yaml:"read_timeout" conf:"default:10"`
- WriteTimeout int `yaml:"write_timeout" conf:"default:10"`
- IdleTimeout int `yaml:"idle_timeout" conf:"default:30"`
+ Port string `yaml:"port" conf:"default:7745"`
+ Host string `yaml:"host"`
+ MaxUploadSize int64 `yaml:"max_file_upload" conf:"default:10"`
+ ReadTimeout time.Duration `yaml:"read_timeout" conf:"default:10s"`
+ WriteTimeout time.Duration `yaml:"write_timeout" conf:"default:10s"`
+ IdleTimeout time.Duration `yaml:"idle_timeout" conf:"default:30s"`
}
// New parses the CLI/Config file and returns a Config struct. If the file argument is an empty string, the
diff --git a/backend/internal/web/mid/errors.go b/backend/internal/web/mid/errors.go
index c619477..c8b04d6 100644
--- a/backend/internal/web/mid/errors.go
+++ b/backend/internal/web/mid/errors.go
@@ -16,7 +16,7 @@ type ErrorResponse struct {
Fields map[string]string `json:"fields,omitempty"`
}
-func Errors(svr *server.Server, log zerolog.Logger) errchain.ErrorHandler {
+func Errors(log zerolog.Logger) errchain.ErrorHandler {
return func(h errchain.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err := h.ServeHTTP(w, r)
@@ -71,14 +71,6 @@ func Errors(svr *server.Server, log zerolog.Logger) errchain.ErrorHandler {
if err := server.JSON(w, code, resp); err != nil {
log.Err(err).Msg("failed to write response")
}
-
- // If Showdown error, return error
- if server.IsShutdownError(err) {
- err := svr.Shutdown(err.Error())
- if err != nil {
- log.Err(err).Msg("failed to shutdown server")
- }
- }
}
})
}
diff --git a/frontend/components/Form/DatePicker.vue b/frontend/components/Form/DatePicker.vue
index 2a681ea..e4bc7bc 100644
--- a/frontend/components/Form/DatePicker.vue
+++ b/frontend/components/Form/DatePicker.vue
@@ -3,7 +3,7 @@
-
+