website/main.go
Dan Anglin a0e3ee8a6f
feat: add a landing page for the Flow Platform
Add a landing page for the Flow Platform. The landing page is a
linktree-styled static page made with Go, HTML and CSS.
2023-08-24 13:15:55 +01:00

144 lines
3 KiB
Go

package main
import (
"embed"
"flag"
"fmt"
"html/template"
"io/fs"
"log/slog"
"net/http"
"os"
"strings"
"time"
)
//go:embed web/static/*
var staticFS embed.FS
//go:embed web/html/*
var htmlTemplates embed.FS
func main() {
var (
address string
profiles links
services links
)
flag.StringVar(&address, "address", "0.0.0.0:8080", "The address that the web server will listen on")
flag.Var(&services, "service", "A list of service links")
flag.Var(&profiles, "profile", "A list of profile links")
flag.Parse()
setupLogging()
mux, err := routes(services, profiles)
if err != nil {
slog.Error("Unable to create the Mux", "error", err)
os.Exit(1)
}
server := http.Server{
Addr: address,
Handler: mux,
ReadHeaderTimeout: 1 * time.Second,
ReadTimeout: 5 * time.Second,
}
slog.Info("Starting web application.", "address", address)
err = server.ListenAndServe()
if err != nil {
slog.Error("Failed to run the web application", "error", err)
}
}
func routes(services, profiles links) (*http.ServeMux, error) {
mux := http.NewServeMux()
staticRootFS, err := fs.Sub(staticFS, "web/static")
if err != nil {
return nil, fmt.Errorf("unable to create the static root file system; %w", err)
}
fileServer := http.FileServer(http.FS(staticRootFS))
mux.Handle("/static/", http.StripPrefix("/static", neuter(fileServer)))
mux.HandleFunc("/", landing(services, profiles))
return mux, nil
}
func neuter(next http.Handler) http.Handler {
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
if strings.HasSuffix(request.URL.Path, "/") {
notFound(writer)
return
}
next.ServeHTTP(writer, request)
})
}
func landing(services, profiles links) func(http.ResponseWriter, *http.Request) {
return func(writer http.ResponseWriter, request *http.Request) {
if request.URL.Path != "/" {
notFound(writer)
return
}
if request.Method != http.MethodGet {
writer.Header().Set("Allow", http.MethodGet)
clientError(writer, http.StatusMethodNotAllowed)
return
}
tmpl, err := template.New("base").ParseFS(htmlTemplates, "web/html/base.tmpl.html")
if err != nil {
serverError(writer, fmt.Errorf("error parsing the HTML template; %w", err))
return
}
links := struct {
Services links
Profiles links
}{
Services: services,
Profiles: profiles,
}
if err = tmpl.Execute(writer, &links); err != nil {
serverError(writer, fmt.Errorf("error rendering the HTML templates; %w", err))
}
}
}
func notFound(w http.ResponseWriter) {
clientError(w, http.StatusNotFound)
}
func clientError(w http.ResponseWriter, status int) {
http.Error(w, http.StatusText(status), status)
}
func serverError(w http.ResponseWriter, err error) {
slog.Error(err.Error())
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
func setupLogging() {
opts := slog.HandlerOptions{
AddSource: false,
}
logger := slog.New(slog.NewJSONHandler(os.Stdout, &opts))
slog.SetDefault(logger)
}