Refactor server and templates package

This commit is contained in:
Tom Wiesing 2023-01-19 13:22:48 +01:00
parent b6bf0a8900
commit 6ede99d7c6
No known key found for this signature in database
105 changed files with 341 additions and 339 deletions

View file

@ -0,0 +1,85 @@
package home
import (
"context"
"fmt"
"net/http"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templates"
"github.com/FAU-CDI/wisski-distillery/internal/status"
"github.com/FAU-CDI/wisski-distillery/pkg/lazy"
)
type Home struct {
component.Base
Dependencies struct {
Templating *templates.Templating
Instances *instances.Instances
}
instanceNames lazy.Lazy[map[string]struct{}] // instance names
homeInstances lazy.Lazy[[]status.WissKI] // list of home instances (updated via cron)
}
var (
_ component.Routeable = (*Home)(nil)
)
func (*Home) Routes() component.Routes {
return component.Routes{
Prefix: "/",
MatchAllDomains: true,
CSRF: false,
MenuTitle: "WissKI Distillery",
MenuPriority: component.MenuHome,
}
}
func (home *Home) HandleRoute(ctx context.Context, route string) (http.Handler, error) {
// 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) instanceMap(ctx context.Context) (map[string]struct{}, error) {
wissKIs, err := home.Dependencies.Instances.All(ctx)
if err != nil {
return nil, err
}
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) {
if _, ok := home.instanceNames.Get(nil)[slug]; !ok {
// Get(nil) guaranteed to work by precondition
w.WriteHeader(http.StatusNotFound)
fmt.Fprintf(w, "WissKI %q not found\n", slug)
return
}
w.WriteHeader(http.StatusBadGateway)
fmt.Fprintf(w, "WissKI %q is currently offline\n", slug)
}

View file

@ -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
}

View file

@ -0,0 +1,44 @@
package home
import (
"context"
_ "embed"
"net/http"
"strings"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templates"
"github.com/FAU-CDI/wisski-distillery/internal/status"
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
)
//go:embed "public.html"
var publicHTML []byte
var publicTemplate = templates.Parse[publicContext]("public.html", publicHTML, assets.AssetsDefault)
type publicContext struct {
templates.BaseContext
Instances []status.WissKI
SelfRedirect string
}
func (home *Home) publicHandler(ctx context.Context) http.Handler {
tpl := publicTemplate.Prepare(home.Dependencies.Templating, templates.BaseContextGaps{
Crumbs: []component.MenuItem{
{Title: "WissKI Distillery", Path: "/"},
},
})
return tpl.HTMLHandler(func(r *http.Request) (pc publicContext, err error) {
// only act on the root path!
if strings.TrimSuffix(r.URL.Path, "/") != "" {
return pc, httpx.ErrNotFound
}
pc.Instances = home.homeInstances.Get(nil)
pc.SelfRedirect = home.Config.SelfRedirect.String()
return
})
}

View file

@ -0,0 +1,36 @@
{{ template "_base.html" . }}
{{ define "title" }}WissKI Distillery{{ end }}
{{ define "content" }}
{{ block "@custom/about" . }}
<div class="pure-u-1">
<p>
For more information, see <a href="{{ .SelfRedirect }}">{{ .SelfRedirect }}</a>.
</p>
</div>
{{ end }}
<div class="pure-u-1">
<h2>WissKIs on this Distillery</h2>
</div>
{{range .Instances}}
{{ if and .Running (not .NoPrefixes) }}
<div class="pure-u-1 pure-u-md-1-3">
<h3>{{.Slug}}</h3>
<p>
<a href="{{.URL}}" target="_blank" rel="noopener noreferrer" class="wisskilink">{{.URL}}</a><br>
<small>
{{ .Statistics.Bundles.Summary }}
{{ $edit := .Statistics.Bundles.LastEdit }}
{{ if $edit.Valid }}
<br />
last edited {{ $edit.Time.Format "2006-01-02T15:04:05Z07:00" }}
{{ end }}
</small>
</p>
</div>
{{ end }}
{{ end }}
{{ end }}

View file

@ -0,0 +1,103 @@
package home
import (
"context"
"encoding/json"
"net/http"
"strings"
)
func (home *Home) loadRedirect(ctx context.Context) (redirect Redirect, err error) {
if redirect.Overrides == nil {
redirect.Overrides = make(map[string]string)
}
delete(redirect.Overrides, "") // make sure there is no root redirect
redirect.Absolute = false
redirect.Permanent = false
// load the overrides file
overrides, err := home.Environment.Open(home.Config.SelfOverridesFile)
if err != nil {
return redirect, err
}
defer overrides.Close()
// decode the overrides file
if err := json.NewDecoder(overrides).Decode(&redirect.Overrides); err != nil {
return redirect, err
}
// and return!
return redirect, nil
}
// Redirect implements a redirect server that redirects all requests.
// It implements http.Handler.
type Redirect struct {
// Target is the target URL to redirect to.
Target string
// Fallback is used when target is the empty string.
Fallback http.Handler
// Absolute determines if the request path should be appended to the target URL when redirecting.
// By default this path is always appended, set Absolute to true to prevent this.
Absolute bool
// Overrides is a map from paths to URLs that should override the default target.
Overrides map[string]string
// Permanent determines if the redirect responses issued should return
// Permanent Redirect (Status Code 308) or Temporary Redirect (Status Code 307).
Permanent bool
}
// Redirect determines the redirect URL for a specific incoming request
// If it returns the empty string, the fallback is used.
func (redirect Redirect) Redirect(r *http.Request) string {
// if we have an override for this URL, use it immediatly
url := strings.TrimSuffix(r.URL.Path, "/")
if override, ok := redirect.Overrides[url]; ok {
return override
}
if redirect.Target == "" {
return ""
}
// if we are in absolute redirect mode, always return the absolute URL
if redirect.Absolute {
return redirect.Target
}
// return the target + the redirected URL
dest := strings.TrimSuffix(redirect.Target, "/") + r.URL.Path
if len(r.URL.RawQuery) > 0 {
dest += "?" + r.URL.RawQuery
}
return dest
}
// ServeHTTP implements the http.Handler interface and redirects a single request to redirect.Target.
func (redirect Redirect) ServeHTTP(w http.ResponseWriter, r *http.Request) {
dest := redirect.Redirect(r)
if dest == "" {
if redirect.Fallback == nil {
http.NotFound(w, r)
return
}
redirect.Fallback.ServeHTTP(w, r)
return
}
// determine if we are temporary or permanent redirect
status := http.StatusTemporaryRedirect
if redirect.Permanent {
status = http.StatusPermanentRedirect
}
// and do the redirect
http.Redirect(w, r, dest, status)
}