Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions cmd/webhix/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,14 @@ func main() {
os.Exit(1)
}

if err := app.Start(ctx, cfg, os.Args[1:]); err != nil {
slog.Error("app run", "err", err)
application, err := app.New(ctx, cfg)
if err != nil {
slog.Error("up app", "err", err)
os.Exit(1)
}

if err := application.Start(ctx); err != nil {
slog.Error("start app", "err", err)
os.Exit(1)
}
}
62 changes: 42 additions & 20 deletions internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,26 @@ package app

import (
"context"
"errors"
"log/slog"
"net/http"
"time"

"github.com/GaIsBAX/Webhix/internal/config"
"github.com/GaIsBAX/Webhix/internal/hub"
"github.com/GaIsBAX/Webhix/internal/server"
"github.com/GaIsBAX/Webhix/internal/server/middleware"
)

const shutdownTimeout = 10 * time.Second
const (
readHeaderTimeout = 5 * time.Second
shutdownTimeout = 10 * time.Second
)

type App struct {
server *http.Server
config *config.Config
deps *dependencies
events *hub.Hub
services *services
server *http.Server

config *config.Config
deps *dependencies
}

func New(ctx context.Context, cfg *config.Config) (*App, error) {
Expand All @@ -26,28 +30,46 @@ func New(ctx context.Context, cfg *config.Config) (*App, error) {
return nil, err
}

services := newServices(deps.repositories)
events := hub.New()

mux, err := newMux(cfg, services, events)
handler, err := newHTTPHandler(deps.mux, cfg)
if err != nil {
if closeErr := deps.close(); closeErr != nil {
return nil, errors.Join(err, closeErr)
}
return nil, err
}

handler, err := newHTTPHandler(cfg, mux)
if err != nil {
return nil, err
server := &http.Server{
Addr: cfg.Addr,
Handler: handler,
ReadHeaderTimeout: readHeaderTimeout,
}

return &App{
server: newHTTPServer(cfg, handler),
config: cfg,
deps: deps,
events: events,
services: services,
server: server,
config: cfg,
deps: deps,
}, nil
}

func newHTTPHandler(mux *http.ServeMux, cfg *config.Config) (http.Handler, error) {
var middlewares []func(http.Handler) http.Handler

if len(cfg.TrustedProxies) > 0 {
trustedProxies := middleware.NewTrustedProxies(cfg.TrustedProxies)
if trustedProxies == nil {
return nil, ErrInvalidTrustedProxies
}
middlewares = append(middlewares, trustedProxies.BehindProxy)
}

if cfg.Password != "" || cfg.SecretKey != "" {
auth := middleware.NewAuth(cfg.Password, cfg.SecretKey)
middlewares = append(middlewares, auth.Protect)
}

return server.Chain(mux, middlewares...), nil
}

func (a *App) Start(ctx context.Context) error {
serverErr := make(chan error, 1)
go func() {
Expand All @@ -68,7 +90,7 @@ func (a *App) Start(ctx context.Context) error {

func (a *App) Shutdown(ctx context.Context) error {
slog.Info("shutting down")
a.events.Close()
a.deps.infra.hub.Close()

shutdownCtx, cancel := context.WithTimeout(ctx, shutdownTimeout)
defer cancel()
Expand Down
127 changes: 106 additions & 21 deletions internal/app/deps.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,56 +4,141 @@ import (
"context"
"errors"
"fmt"
"net/http"

"github.com/GaIsBAX/Webhix/internal/config"
"github.com/GaIsBAX/Webhix/internal/core"
"github.com/GaIsBAX/Webhix/internal/hub"
"github.com/GaIsBAX/Webhix/internal/repos"
"github.com/GaIsBAX/Webhix/internal/server"
"github.com/GaIsBAX/Webhix/internal/store"
"github.com/GaIsBAX/Webhix/pkg"
)

type dependencies struct {
db *store.Database
repositories *repositories
mux *http.ServeMux
cfg *config.Config

infra *infrastructure
repos *repositories
services *services
handlers *handlers
}

func newDependencies(ctx context.Context, cfg *config.Config) (*dependencies, error) {
db, err := store.New(ctx, cfg.DBPath)
var deps dependencies

mux := http.NewServeMux()

infra, err := newInfrastructure(ctx, cfg)
if err != nil {
return nil, fmt.Errorf("open database: %w", err)
return nil, err
}

if err := db.Migrate(); err != nil {
if closeErr := db.Close(); closeErr != nil {
return nil, errors.Join(
fmt.Errorf("migrate database: %w", err),
fmt.Errorf("close database after migration failure: %w", closeErr),
)
}
repos := newRepositories(infra.db)
services := newServices(repos)

return nil, fmt.Errorf("migrate database: %w", err)
}
deps.mux = mux
deps.cfg = cfg

return &dependencies{
db: db,
repositories: newRepositories(db),
}, nil
deps.infra = infra
deps.repos = repos
deps.services = services
deps.handlers = newHandlers(&deps)
deps.handlers.registerRoutes()

return &deps, nil
}

type services struct {
hook *core.Hook
serve *core.Serve
version *core.Version
}

func newServices(repos *repositories) *services {
hook := core.NewHook(repos.hook, func() string {
return pkg.GeneratePrefixedString("ho")
})
serve := core.NewServe(repos.serve)
version := core.NewVersion()

return &services{
hook: hook,
serve: serve,
version: version,
}
}

type repositories struct {
hook *repos.HookRepository
hook *repos.Hook
serve *repos.Serve
}

func newRepositories(db *store.Database) *repositories {
return &repositories{
hook: repos.NewHookRepository(db.DB),
hook: repos.NewHook(db.DB),
serve: repos.NewServe(db.DB),
}
}

func (d *dependencies) close() error {
if d.db != nil {
return d.db.Close()
if d.infra.db != nil {
return d.infra.db.Close()
}

return nil
}

type infrastructure struct {
db *store.Database
hub *hub.Hub
}

func newInfrastructure(ctx context.Context, cfg *config.Config) (*infrastructure, error) {
db, err := store.New(ctx, cfg.DBPath)
if err != nil {
return nil, err
}

if err := db.Migrate(); err != nil {
if closeErr := db.Close(); closeErr != nil {
return nil, errors.Join(
fmt.Errorf("%w: %w", ErrMigrateDatabase, err),
fmt.Errorf("%w after migration failure: %w", ErrCloseDatabase, closeErr),
)
}

return nil, fmt.Errorf("%w: %w", ErrMigrateDatabase, err)
}

hub := hub.New()

return &infrastructure{
db: db,
hub: hub,
}, nil
}

type handlers struct {
hook *server.Hook
}

func newHandlers(deps *dependencies) *handlers {
return &handlers{
hook: server.NewHook(&server.HookDeps{
Mux: deps.mux,
Service: deps.services.hook,
Hub: deps.infra.hub,
Opts: server.HookOptions{
BaseURL: deps.cfg.BaseURL,
MaxBodySize: deps.cfg.MaxBodySize,
ReadOnly: deps.cfg.ReadOnly,
},
}),
}
}

func (h *handlers) registerRoutes() {
h.hook.RegisterRoutes()
}
12 changes: 12 additions & 0 deletions internal/app/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package app

import "errors"

var (
ErrOpenDatabase = errors.New("open database")
ErrMigrateDatabase = errors.New("migrate database")
ErrCloseDatabase = errors.New("close database")
ErrAuthSetup = errors.New("auth setup")
ErrAuthRequired = errors.New("auth is required")
ErrInvalidTrustedProxies = errors.New("invalid trusted proxies")
)
Loading