diff --git a/internal/dis/component/control/home/cron.go b/internal/dis/component/control/home/cron.go deleted file mode 100644 index e371f7a..0000000 --- a/internal/dis/component/control/home/cron.go +++ /dev/null @@ -1,83 +0,0 @@ -package home - -import ( - "context" - - "github.com/FAU-CDI/wisski-distillery/internal/dis/component" - "github.com/FAU-CDI/wisski-distillery/pkg/httpx" -) - -type UpdateInstanceList struct { - component.Base - Dependencies struct { - Home *Home - } -} - -var ( - _ component.Cronable = (*UpdateInstanceList)(nil) -) - -func (*UpdateInstanceList) TaskName() string { - return "instance list" -} - -func (ul *UpdateInstanceList) Cron(ctx context.Context) error { - names, err := ul.Dependencies.Home.instanceMap(ctx) - if err != nil { - return err - } - - ul.Dependencies.Home.instanceNames.Set(names) - return nil -} - -type UpdateRedirect struct { - component.Base - Dependencies struct { - Home *Home - } -} - -var ( - _ component.Cronable = (*UpdateRedirect)(nil) -) - -func (ur *UpdateRedirect) TaskName() string { - return "redirect" -} - -func (ur *UpdateRedirect) Cron(ctx context.Context) error { - redirect, err := ur.Dependencies.Home.loadRedirect(ctx) - if err != nil { - return err - } - - ur.Dependencies.Home.redirect.Set(&redirect) - return nil -} - -type UpdateHome struct { - component.Base - Dependencies struct { - Home *Home - } -} - -var ( - _ component.Cronable = (*UpdateHome)(nil) -) - -func (ur *UpdateHome) TaskName() string { - return "home render" -} - -func (ur *UpdateHome) Cron(ctx context.Context) error { - bytes, err := ur.Dependencies.Home.homeRender(ctx) - if err != nil { - return err - } - - ur.Dependencies.Home.homeBytes.Set(httpx.MinifyHTML(bytes)) - return nil -} diff --git a/internal/dis/component/control/home/home.go b/internal/dis/component/control/home/home.go index 39b0350..02d7044 100644 --- a/internal/dis/component/control/home/home.go +++ b/internal/dis/component/control/home/home.go @@ -3,12 +3,12 @@ package home import ( "context" "fmt" - "html/template" "net/http" "github.com/FAU-CDI/wisski-distillery/internal/dis/component" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static/custom" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances" + "github.com/FAU-CDI/wisski-distillery/internal/status" "github.com/FAU-CDI/wisski-distillery/pkg/lazy" ) @@ -19,10 +19,8 @@ type Home struct { Custom *custom.Custom } - redirect lazy.Lazy[*Redirect] - instanceNames lazy.Lazy[map[string]struct{}] - homeBytes lazy.Lazy[[]byte] - homeTemplate lazy.Lazy[*template.Template] + instanceNames lazy.Lazy[map[string]struct{}] // instance names + homeInstances lazy.Lazy[[]status.WissKI] // list of home instances (updated via cron) } var ( @@ -37,31 +35,37 @@ func (*Home) Routes() component.Routes { } func (home *Home) HandleRoute(ctx context.Context, route string) (http.Handler, error) { - return home, nil + // generate a default handler + dflt, err := home.loadRedirect(ctx) + if err != nil { + return nil, err + } + dflt.Fallback = home.publicHandler(ctx) + + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + slug, ok := home.Config.SlugFromHost(r.Host) + switch { + case !ok: + http.NotFound(w, r) + case slug != "": + home.serveWissKI(w, slug, r) + default: + dflt.ServeHTTP(w, r) + } + }), nil } -func (home *Home) ServeHTTP(w http.ResponseWriter, r *http.Request) { - slug, ok := home.Config.SlugFromHost(r.Host) - switch { - case !ok: - http.NotFound(w, r) - case slug != "": - home.serveWissKI(w, slug, r) - default: - home.serveRoot(w, r) - } -} - -func (home *Home) serveRoot(w http.ResponseWriter, r *http.Request) { - // not the root url => server the fallback - if !(r.URL.Path == "" || r.URL.Path == "/") { - home.redirect.Get(nil).ServeHTTP(w, r) - return +func (home *Home) instanceMap(ctx context.Context) (map[string]struct{}, error) { + wissKIs, err := home.Dependencies.Instances.All(ctx) + if err != nil { + return nil, err } - w.Header().Set("Content-Type", "text/html") - w.WriteHeader(http.StatusAccepted) - w.Write(home.homeBytes.Get(nil)) + names := make(map[string]struct{}, len(wissKIs)) + for _, w := range wissKIs { + names[w.Slug] = struct{}{} + } + return names, nil } func (home *Home) serveWissKI(w http.ResponseWriter, slug string, r *http.Request) { diff --git a/internal/dis/component/control/home/instances.go b/internal/dis/component/control/home/instances.go new file mode 100644 index 0000000..0bceca2 --- /dev/null +++ b/internal/dis/component/control/home/instances.go @@ -0,0 +1,86 @@ +package home + +import ( + "context" + + "github.com/FAU-CDI/wisski-distillery/internal/dis/component" + "github.com/FAU-CDI/wisski-distillery/internal/status" + "golang.org/x/sync/errgroup" +) + +// loadInstances loads all the instances into the home route +func (home *Home) loadInstances(ctx context.Context) ([]status.WissKI, error) { + // find all the WissKIs + wissKIs, err := home.Dependencies.Instances.All(ctx) + if err != nil { + return nil, err + } + + instances := make([]status.WissKI, len(wissKIs)) + + // determine their infos + var eg errgroup.Group + for i, instance := range wissKIs { + i := i + wissKI := instance + eg.Go(func() (err error) { + instances[i], err = wissKI.Info().Information(ctx, false) + return + }) + } + eg.Wait() + + // and return the new instances + return instances, nil +} + +// UpdateInstanceList updates the instances list of the home struct +type UpdateInstanceList struct { + component.Base + Dependencies struct { + Home *Home + } +} + +var ( + _ component.Cronable = (*UpdateInstanceList)(nil) +) + +func (*UpdateInstanceList) TaskName() string { + return "instance list" +} + +func (ul *UpdateInstanceList) Cron(ctx context.Context) error { + names, err := ul.Dependencies.Home.instanceMap(ctx) + if err != nil { + return err + } + + ul.Dependencies.Home.instanceNames.Set(names) + return nil +} + +type UpdateHome struct { + component.Base + Dependencies struct { + Home *Home + } +} + +var ( + _ component.Cronable = (*UpdateHome)(nil) +) + +func (ur *UpdateHome) TaskName() string { + return "home instances fetch" +} + +func (ur *UpdateHome) Cron(ctx context.Context) error { + instances, err := ur.Dependencies.Home.loadInstances(ctx) + if err != nil { + return err + } + + ur.Dependencies.Home.homeInstances.Set(instances) + return nil +} diff --git a/internal/dis/component/control/home/public.go b/internal/dis/component/control/home/public.go index fbf7ba1..33e71d0 100644 --- a/internal/dis/component/control/home/public.go +++ b/internal/dis/component/control/home/public.go @@ -1,77 +1,43 @@ package home import ( - "bytes" "context" - "html/template" - "time" - _ "embed" + "net/http" + "strings" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static/custom" "github.com/FAU-CDI/wisski-distillery/internal/status" - "golang.org/x/sync/errgroup" + "github.com/FAU-CDI/wisski-distillery/pkg/httpx" ) -func (home *Home) instanceMap(ctx context.Context) (map[string]struct{}, error) { - wissKIs, err := home.Dependencies.Instances.All(ctx) - if err != nil { - return nil, err - } +//go:embed "public.html" +var publicHTMLStr string +var publicTemplate = static.AssetsHome.MustParseShared("public.html", publicHTMLStr) - names := make(map[string]struct{}, len(wissKIs)) - for _, w := range wissKIs { - names[w.Slug] = struct{}{} - } - return names, nil -} - -//go:embed "home.html" -var homeHTMLStr string -var homeTemplate = static.AssetsHome.MustParseShared("home.html", homeHTMLStr) - -func (home *Home) homeRender(ctx context.Context) ([]byte, error) { - var context homeContext - home.Dependencies.Custom.Update(&context, nil) - - // setup a couple of static things - context.Time = time.Now().UTC() - context.SelfRedirect = home.Config.SelfRedirect.String() - - // find all the WissKIs - wissKIs, err := home.Dependencies.Instances.All(ctx) - if err != nil { - return nil, err - } - context.Instances = make([]status.WissKI, len(wissKIs)) - - // determine their infos - var eg errgroup.Group - for i, instance := range wissKIs { - i := i - wissKI := instance - eg.Go(func() (err error) { - context.Instances[i], err = wissKI.Info().Information(ctx, false) - return - }) - } - eg.Wait() - - // render the template - var buffer bytes.Buffer - err = home.homeTemplate.Get(func() *template.Template { - return home.Dependencies.Custom.Template(homeTemplate) - }).Execute(&buffer, context) - return buffer.Bytes(), err -} - -type homeContext struct { +type publicContext struct { custom.BaseContext - Instances []status.WissKI - - Time time.Time - + Instances []status.WissKI SelfRedirect string } + +func (home *Home) publicHandler(ctx context.Context) http.Handler { + return httpx.HTMLHandler[publicContext]{ + Handler: func(r *http.Request) (pc publicContext, err error) { + // only act on the root path! + if strings.TrimSuffix(r.URL.Path, "/") != "" { + return pc, httpx.ErrNotFound + } + + home.Dependencies.Custom.Update(&pc, r) + + pc.Instances = home.homeInstances.Get(nil) + pc.SelfRedirect = home.Config.SelfRedirect.String() + + return + }, + Template: home.Dependencies.Custom.Template(publicTemplate), + } +} diff --git a/internal/dis/component/control/home/home.html b/internal/dis/component/control/home/public.html similarity index 100% rename from internal/dis/component/control/home/home.html rename to internal/dis/component/control/home/public.html diff --git a/internal/dis/component/control/home/redirect.go b/internal/dis/component/control/home/redirect.go index e605803..e3ee615 100644 --- a/internal/dis/component/control/home/redirect.go +++ b/internal/dis/component/control/home/redirect.go @@ -11,7 +11,8 @@ func (home *Home) loadRedirect(ctx context.Context) (redirect Redirect, err erro if redirect.Overrides == nil { redirect.Overrides = make(map[string]string) } - redirect.Overrides[""] = home.Config.SelfRedirect.String() + + delete(redirect.Overrides, "") // make sure there is no root redirect redirect.Absolute = false redirect.Permanent = false diff --git a/internal/dis/component/control/news/news.go b/internal/dis/component/control/news/news.go index b86ef1f..cf4d23f 100644 --- a/internal/dis/component/control/news/news.go +++ b/internal/dis/component/control/news/news.go @@ -13,7 +13,7 @@ import ( "github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static/custom" "github.com/FAU-CDI/wisski-distillery/pkg/httpx" - "github.com/FAU-CDI/wisski-distillery/pkg/lazy" + "github.com/rs/zerolog" "github.com/yuin/goldmark" gmmeta "github.com/yuin/goldmark-meta" "github.com/yuin/goldmark/parser" @@ -84,31 +84,28 @@ func (item *Item) parse(path string, builder *strings.Builder) error { //go:embed "NEWS/*.md" var newsFS embed.FS -var news lazy.Lazy[[]Item] - // Items returns a list of all news items -func Items() []Item { - return news.Get(func() (items []Item) { - var builder strings.Builder +func Items() ([]Item, error) { + var builder strings.Builder - files, err := fs.Glob(newsFS, "NEWS/*.md") - if err != nil { - panic(err) - } - items = make([]Item, len(files)) - for i, file := range files { - items[i].ID = file[len("NEWS/") : len(file)-len(".md")] - if err := items[i].parse(file, &builder); err != nil { - panic(err) - } + files, err := fs.Glob(newsFS, "NEWS/*.md") + if err != nil { + return nil, err + } + + items := make([]Item, len(files)) + for i, file := range files { + items[i].ID = file[len("NEWS/") : len(file)-len(".md")] + if err := items[i].parse(file, &builder); err != nil { + return nil, err } + } - slices.SortFunc(items, func(a, b Item) bool { - return !a.Date.Before(b.Date) - }) - - return + slices.SortFunc(items, func(a, b Item) bool { + return !a.Date.Before(b.Date) }) + + return items, nil } //go:embed "news.html" @@ -122,14 +119,22 @@ type newsContext struct { // HandleRoute returns the handler for the requested path func (news *News) HandleRoute(ctx context.Context, path string) (http.Handler, error) { - newsTemplate := news.Dependencies.Custom.Template(newsTemplate) + items, itemsErr := Items() + if itemsErr != nil { + zerolog.Ctx(ctx).Err(itemsErr).Msg("Unable to load news items") + } return httpx.HTMLHandler[newsContext]{ Handler: func(r *http.Request) (nc newsContext, err error) { + if strings.TrimSuffix(r.URL.Path, "/") != strings.TrimSuffix(path, "/") { + return nc, httpx.ErrNotFound + } + news.Dependencies.Custom.Update(&nc, r) - nc.Items = Items() + nc.Items, err = items, itemsErr + return }, - Template: newsTemplate, + Template: news.Dependencies.Custom.Template(newsTemplate), }, nil } diff --git a/internal/dis/component/control/static/custom/context.go b/internal/dis/component/control/static/custom/context.go index e1059c4..e5aca8d 100644 --- a/internal/dis/component/control/static/custom/context.go +++ b/internal/dis/component/control/static/custom/context.go @@ -20,7 +20,8 @@ var baseContextName = reflectx.TypeOf[BaseContext]().Name() // This context should always be initialized using the [custom.Update], [custom.New] or [custom.NewForm] functions. // Other invocations might cause an error at runtime. type BaseContext struct { - inited bool // has this context been inited + inited bool // has this context been inited? + requestWasNil bool // was the passed request nil GeneratedAt time.Time // time this page was generated at @@ -32,14 +33,19 @@ const ( errorPrefix template.HTML = `
BaseContext.use() not called" + errorSuffix
+ requestNilError template.HTML = errorPrefix + "BaseContext.use() called with nil request" + errorSuffix
)
// Use updates this context to use the values from the given base.
+//
+// The given request *must not* be nil.
+//
// For convenience the passed context is also returned.
func (tc *BaseContext) use(base component.Base, r *http.Request) *BaseContext {
tc.inited = true
+ tc.requestWasNil = r == nil
tc.GeneratedAt = time.Now().UTC()
@@ -56,6 +62,9 @@ func (bc BaseContext) DoInitCheck() template.HTML {
if !bc.inited {
return initError
}
+ if bc.requestWasNil {
+ return requestNilError
+ }
return ""
}
diff --git a/internal/dis/component/control/static/templates/_base.html b/internal/dis/component/control/static/templates/_base.html
index 59cff02..789e9f1 100644
--- a/internal/dis/component/control/static/templates/_base.html
+++ b/internal/dis/component/control/static/templates/_base.html
@@ -4,7 +4,7 @@
-