re-organise everything
1 file changed, 135 insertions(+), 8 deletions(-)
changed files
M internal/website/mux.go → internal/website/mux.go
@@ -2,19 +2,42 @@ package website import ( "encoding/json" + "fmt" "net/http" + "regexp" + "slices" "strings" + "gitlab.com/tozd/go/errors" + "go.alanpearce.eu/website/internal/builder" "go.alanpearce.eu/website/internal/config" ihttp "go.alanpearce.eu/website/internal/http" + "go.alanpearce.eu/website/internal/server" "go.alanpearce.eu/website/internal/storage" + "go.alanpearce.eu/website/internal/storage/sqlite" + "go.alanpearce.eu/website/internal/watcher" "go.alanpearce.eu/website/templates" "go.alanpearce.eu/x/log" "github.com/benpate/digit" "github.com/kevinpollet/nego" + "github.com/osdevisnot/sorvor/pkg/livereload" ) +type Options struct { + DBPath string + Redirect bool + Development bool + Config *config.Config +} + +type Website struct { + config *config.Config + log *log.Logger + reader storage.Reader + *server.App +} + type webHandler func(http.ResponseWriter, *http.Request) *ihttp.Error type WrappedWebHandler struct {@@ -51,22 +74,74 @@ } } } -func NewMux( - cfg *config.Config, - reader storage.Reader, +func New( + opts *Options, log *log.Logger, -) (mux *http.ServeMux, err error) { - mux = &http.ServeMux{} +) (*Website, error) { + website := &Website{ + config: opts.Config, + log: log, + } + builderOptions := &builder.Options{} + + mux := &http.ServeMux{} templates.Setup() + cfg := opts.Config + + db, err := sqlite.OpenDB(opts.DBPath) + if err != nil { + return nil, errors.WithMessage(err, "error opening database") + } + + builderOptions.DB = db + + if opts.Development { + liveReload := livereload.New() + mux.Handle("/_/reload", liveReload) + liveReload.Start() + fw, err := watcher.New(log.Named("watcher")) + if err != nil { + return nil, errors.WithMessage(err, "could not create file watcher") + } + for _, dir := range []string{"content", "static", "templates", "internal/builder"} { + err := fw.AddRecursive(dir) + if err != nil { + return nil, errors.WithMessagef( + err, + "could not add directory %s to file watcher", + dir, + ) + } + } + // TODO implement rebuilding + // go fw.Start(func(filename string) { + // log.Info("rebuilding site", "changed_file", filename) + // err := rebuild(builderOptions, cfg, log) + // if err != nil { + // log.Error("error rebuilding site", "error", err) + // } + // }) + } + + err = rebuild(builderOptions, cfg, log) + if err != nil { + return nil, errors.WithMessage(err, "could not build site") + } + + website.reader, err = sqlite.NewReader(db, log.Named("reader")) + if err != nil { + return nil, errors.WithMessage(err, "error creating sqlite reader") + } + mux.Handle("/", wrapHandler(cfg, func(w http.ResponseWriter, r *http.Request) *ihttp.Error { - urlPath, shouldRedirect := reader.CanonicalisePath(r.URL.Path) + urlPath, shouldRedirect := website.reader.CanonicalisePath(r.URL.Path) if shouldRedirect { http.Redirect(w, r, urlPath, 302) return nil } - file, err := reader.GetFile(urlPath) + file, err := website.reader.GetFile(urlPath) if err != nil { log.Error("error getting file from reader", "err", err) return &ihttp.Error{@@ -130,5 +205,57 @@ u := cfg.OIDCHost.JoinPath(oidcPath) http.Redirect(w, r, u.String(), 302) }) - return mux, nil + website.App = &server.App{ + Domain: cfg.Domains[0], + Handler: mux, + } + + return website, nil +} + +func (website *Website) MakeRedirectorApp() *server.App { + mux := http.NewServeMux() + + re := regexp.MustCompile( + "^(.*)\\." + strings.ReplaceAll(website.config.WildcardDomain, ".", `\.`) + "$", + ) + replace := "${1}." + website.config.Domains[0] + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + switch { + case slices.Contains(website.config.Domains, r.Host): + path, _ := website.reader.CanonicalisePath(r.URL.Path) + http.Redirect( + w, + r, + website.config.BaseURL.JoinPath(path).String(), + http.StatusMovedPermanently, + ) + case re.MatchString(r.Host): + url := website.config.BaseURL.JoinPath() + url.Host = re.ReplaceAllString(r.Host, replace) + http.Redirect(w, r, url.String(), http.StatusTemporaryRedirect) + case true: + http.NotFound(w, r) + } + }) + + return &server.App{ + Handler: mux, + } +} + +func updateCSPHashes(config *config.Config, r *builder.Result) { + for i, h := range r.Hashes { + config.CSP.StyleSrc[i] = fmt.Sprintf("'%s'", h) + } +} + +func rebuild(builderConfig *builder.Options, config *config.Config, log *log.Logger) error { + r, err := builder.BuildSite(builderConfig, config, log.Named("builder")) + if err != nil { + return errors.WithMessage(err, "could not build site") + } + updateCSPHashes(config, r) + + return nil }