Use go.uber.com/fx for bootstrapping the app
This commit is contained in:
parent
02f4a881a9
commit
9d3232f00a
8 changed files with 144 additions and 59 deletions
12
go.mod
12
go.mod
|
@ -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
16
go.sum
|
@ -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=
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
10
palmatum/internal/httpsrv/sites.go
Normal file
10
palmatum/internal/httpsrv/sites.go
Normal 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))
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue