Use go.uber.com/fx for bootstrapping the app

This commit is contained in:
akp 2024-11-12 23:54:35 +00:00
parent 02f4a881a9
commit 9d3232f00a
No known key found for this signature in database
GPG key ID: CF8D58F3DEB20755
8 changed files with 144 additions and 59 deletions

12
go.mod
View file

@ -3,12 +3,18 @@ module github.com/codemicro/palmatum
go 1.20
require (
github.com/google/uuid v1.6.0
github.com/jmoiron/sqlx v1.4.0
github.com/julienschmidt/httprouter v1.3.0
github.com/mattn/go-sqlite3 v1.14.24
go.akpain.net/cfger v0.2.1
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b
gopkg.in/yaml.v3 v3.0.1
go.uber.org/fx v1.23.0
)
require github.com/google/uuid v1.6.0 // indirect
require (
go.uber.org/dig v1.18.0 // indirect
go.uber.org/multierr v1.10.0 // indirect
go.uber.org/zap v1.26.0 // indirect
golang.org/x/sys v0.1.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

16
go.sum
View file

@ -1,5 +1,6 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
@ -13,10 +14,21 @@ github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
go.akpain.net/cfger v0.2.1 h1:EXbJqxIAJWuYvYX/HqaG85u2Ikk2Xs1foLTUsnIz7bQ=
go.akpain.net/cfger v0.2.1/go.mod h1:uaeo30IdnyNNBIEAT0SwvGIWGBauxMI+THVbk8L0oTs=
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b h1:r+vk0EmXNmekl0S0BascoeeoHk/L7wmaW2QF90K+kYI=
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
go.uber.org/dig v1.18.0 h1:imUL1UiY0Mg4bqbFfsRQO5G4CGRBec/ZujWTvSVp3pw=
go.uber.org/dig v1.18.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE=
go.uber.org/fx v1.23.0 h1:lIr/gYWQGfTwGcSXWXu4vP5Ws6iqnNEIY+F/aFzCKTg=
go.uber.org/fx v1.23.0/go.mod h1:o/D9n+2mLP6v1EG+qsdT1O8wKopYAsqZasju97SDFCU=
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View file

@ -2,7 +2,6 @@ package config
import (
"go.akpain.net/cfger"
"log/slog"
)
type HTTP struct {
@ -47,9 +46,5 @@ func Load() (*Config, error) {
},
}
if conf.Debug {
slog.Debug("debug mode enabled")
}
return conf, nil
}

View file

@ -4,14 +4,15 @@ import (
"database/sql"
"errors"
"fmt"
"github.com/codemicro/palmatum/palmatum/internal/config"
"github.com/jmoiron/sqlx"
_ "github.com/mattn/go-sqlite3"
)
const programSchemaVersion = 1
func New(fname string) (*sqlx.DB, error) {
db, err := sqlx.Connect("sqlite3", fname)
func New(conf *config.Config) (*sqlx.DB, error) {
db, err := sqlx.Connect("sqlite3", conf.Database.DSN)
if err != nil {
return nil, fmt.Errorf("open database: %w", err)
}

View file

@ -1,31 +1,48 @@
package httpsrv
import (
"context"
"github.com/codemicro/palmatum/palmatum/internal/config"
"github.com/codemicro/palmatum/palmatum/internal/core"
"github.com/julienschmidt/httprouter"
"go.uber.org/fx"
"log/slog"
"net/http"
"strings"
)
func New(conf *config.Config, c *core.Core) (http.Handler, error) {
r := &routes{
config: conf,
core: c,
}
type ServerArgs struct {
fx.In
router := httprouter.New()
router.GET("/-/", r.managementIndex)
router.POST("/-/upload", r.uploadSite)
router.POST("/-/delete", r.deleteSite)
return router, nil
Lifecycle fx.Lifecycle
Shutdowner fx.Shutdowner
Logger *slog.Logger
Config *config.Config
Core *core.Core
}
type routes struct {
config *config.Config
core *core.Core
func newServer(args ServerArgs, addr string, handler http.Handler) *http.Server {
server := &http.Server{
Addr: addr,
Handler: handler,
}
args.Lifecycle.Append(fx.Hook{
OnStart: func(context.Context) error {
args.Logger.Info("http server alive", "address", addr)
go func() {
if err := server.ListenAndServe(); err != nil {
args.Logger.Error("failed to start HTTP server", "address", addr, "error", err)
_ = args.Shutdowner.Shutdown(fx.ExitCode(2))
}
}()
return nil
},
OnStop: func(ctx context.Context) error {
return server.Shutdown(ctx)
},
})
return server
}
func BadRequestResponse(w http.ResponseWriter, message ...string) error {

View file

@ -4,6 +4,8 @@ import (
_ "embed"
"errors"
"fmt"
"github.com/codemicro/palmatum/palmatum/internal/config"
"github.com/codemicro/palmatum/palmatum/internal/core"
"github.com/codemicro/palmatum/palmatum/internal/database"
"github.com/julienschmidt/httprouter"
"html/template"
@ -13,6 +15,30 @@ import (
"regexp"
)
func NewManagementServer(args ServerArgs) *http.Server {
return newServer(args, fmt.Sprintf("%s:%d", args.Config.HTTP.Host, args.Config.HTTP.Port), New(args.Config, args.Core))
}
func New(conf *config.Config, c *core.Core) http.Handler {
r := &routes{
config: conf,
core: c,
}
router := httprouter.New()
router.GET("/-/", r.managementIndex)
router.POST("/-/upload", r.uploadSite)
router.POST("/-/delete", r.deleteSite)
return router
}
type routes struct {
config *config.Config
core *core.Core
}
//go:embed managementIndex.html
var managementPageTemplateSource string
var managementPageTemplate *template.Template

View file

@ -0,0 +1,10 @@
package httpsrv
import (
"fmt"
"net/http"
)
func NewSitesServer(args ServerArgs) *http.Server {
return newServer(args, fmt.Sprintf("%s:%d", args.Config.HTTP.Host, args.Config.HTTP.Port+2), New(args.Config, args.Core))
}

View file

@ -1,49 +1,67 @@
package main
import (
"fmt"
"github.com/codemicro/palmatum/palmatum/internal/config"
"github.com/codemicro/palmatum/palmatum/internal/core"
"github.com/codemicro/palmatum/palmatum/internal/database"
"github.com/codemicro/palmatum/palmatum/internal/httpsrv"
"go.uber.org/fx"
"go.uber.org/fx/fxevent"
"log/slog"
"net/http"
"os"
)
func main() {
if err := run(); err != nil {
slog.Error("unhandled error", "error", err)
os.Exit(1)
}
fx.New(
fx.Provide(provideLogger),
fx.WithLogger(provideFxLogger),
fx.Provide(
config.Load,
// TODO: add a graceful shutdown to the database
database.New,
core.New,
fx.Annotate(
httpsrv.NewManagementServer,
fx.ResultTags(`group:"servers"`),
),
fx.Annotate(
httpsrv.NewSitesServer,
fx.ResultTags(`group:"servers"`),
),
),
fx.Invoke(
func(conf *config.Config) {
_ = os.MkdirAll(conf.Platform.SitesDirectory, 0777)
},
fx.Annotate(
func([]*http.Server) {},
fx.ParamTags(`group:"servers"`),
),
),
).Run()
}
func run() error {
conf, err := config.Load()
if err != nil {
return fmt.Errorf("load config on startup: %w", err)
func provideLogger(conf *config.Config) *slog.Logger {
level := new(slog.LevelVar)
l := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: level}))
if conf.Debug {
level.Set(slog.LevelDebug)
l.Debug("debug mode enabled")
}
db, err := database.New(conf.Database.DSN)
if err != nil {
return fmt.Errorf("create database: %w", err)
}
_ = db
_ = os.MkdirAll(conf.Platform.SitesDirectory, 0777)
handler, err := httpsrv.New(conf, core.New(conf, db))
if err != nil {
return fmt.Errorf("creating HTTP handler: %w", err)
}
host := fmt.Sprintf("%s:%d", conf.HTTP.Host, conf.HTTP.Port)
slog.Info("http server alive", "host", host)
err = http.ListenAndServe(host, handler)
if err != nil {
return fmt.Errorf("serving HTTP: %w", err)
}
return nil
return l
}
func provideFxLogger(l *slog.Logger) fxevent.Logger {
fxel := &fxevent.SlogLogger{
Logger: l.With("area", "fx"),
}
fxel.UseLogLevel(slog.LevelDebug)
return fxel
}