WIP: New Flow website built with Nanoc and Mage #1

Draft
dananglin wants to merge 9 commits from new-site into main
11 changed files with 0 additions and 384 deletions
Showing only changes of commit 5d8af62491 - Show all commits

View file

@ -1,7 +0,0 @@
FROM scratch
ADD landing /landing
USER 65534
ENTRYPOINT ["/landing"]

21
LICENSE
View file

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2023 Dan Anglin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

5
go.mod
View file

@ -1,5 +0,0 @@
module codeflow.dananglin.me.uk/flow/landing
go 1.21.0
require github.com/magefile/mage v1.15.0

2
go.sum
View file

@ -1,2 +0,0 @@
github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=

View file

@ -1,75 +0,0 @@
package main
import (
"fmt"
"strings"
)
type linkParseError struct {
msg string
}
func (e linkParseError) Error() string {
return "link parse error: " + e.msg
}
type link struct {
Title string
URL string
Rel string
}
func (l link) String() string {
return fmt.Sprintf("Title: %s, URL: %s, Rel: %s", l.Title, l.URL, l.Rel)
}
type links []link
func (l *links) Set(value string) error {
linkAttributes := strings.Split(value, ",")
lenLinkAttributes := len(linkAttributes)
if lenLinkAttributes != 2 && lenLinkAttributes != 3 {
return linkParseError{msg: fmt.Sprintf("unexpected number of attributes found %s, want 2 or 3, got %d", value, lenLinkAttributes)}
}
var thisLink link
for _, attr := range linkAttributes {
split := strings.Split(attr, "=")
switch strings.ToLower(split[0]) {
case "title":
thisLink.Title = split[1]
case "url":
thisLink.URL = split[1]
case "rel":
thisLink.Rel = split[1]
}
}
if thisLink.Title == "" {
return linkParseError{msg: fmt.Sprintf("the title attribute is missing from %s", value)}
}
if thisLink.URL == "" {
return linkParseError{msg: fmt.Sprintf("the URL attribute is missing from %s", value)}
}
*l = append(*l, thisLink)
return nil
}
func (l *links) String() string {
if len(*l) == 0 {
return ""
}
var builder strings.Builder
for _, link := range *l {
fmt.Fprintln(&builder, link)
}
return builder.String()
}

View file

@ -1,60 +0,0 @@
//go:build mage
// +build mage
package main
import (
"os"
"github.com/magefile/mage/sh"
)
var Default = Build
var binary = "landing"
// Test run the go tests.
// To enable verbose mode set GO_TEST_VERBOSE=1.
// To enable coverage mode set GO_TEST_COVER=1.
func Test() error {
goTest := sh.RunCmd("go", "test")
args := []string{"./..."}
if os.Getenv("GO_TEST_VERBOSE") == "1" {
args = append(args, "-v")
}
if os.Getenv("GO_TEST_COVER") == "1" {
args = append(args, "-cover")
}
return goTest(args...)
}
// Lint runs golangci-lint against the code.
func Lint() error {
return sh.RunV("golangci-lint", "run", "--color", "always")
}
// Build build the executable.
func Build() error {
os.Setenv("GOOS", "linux")
os.Setenv("GOARCH", "amd64")
os.Setenv("CGO_ENABLED", "0")
return sh.Run("go", "build", "-ldflags=-s -w", "-a", "-o", binary, ".")
}
// Clean clean the workspace.
func Clean() error {
if err := sh.Rm(binary); err != nil {
return err
}
if err := sh.Run("go", "clean", "./..."); err != nil {
return err
}
return nil
}

144
main.go
View file

@ -1,144 +0,0 @@
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)
}

View file

@ -1,31 +0,0 @@
{{ define "base" }}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Dan Anglin | Flow Platform</title>
<link rel="stylesheet" href="/static/css/stylesheet.css">
<link rel="icon" type="image/x-icon" href="/static/img/favicon.ico">
</head>
<body>
<h1>Flow Platform</h1>
{{ if gt (len .Services) 0 }}
<h2>Services</h2>
<div class="links">
{{- range .Services }}
<p><a href="{{ .URL }}"{{ if gt (len .Rel) 0 }} rel="{{ .Rel }}"{{ end }}>{{ .Title }}</a></p>
{{ end }}
</div>
{{ end }}
{{ if gt (len .Profiles) 0 }}
<h2>My Profiles</h2>
<div class="links">
{{- range .Profiles }}
<p><a href="{{ .URL }}"{{ if gt (len .Rel) 0 }} rel="{{ .Rel }}"{{ end }}>{{ .Title }}</a></p>
{{ end }}
</div>
{{ end }}
</body>
</html>
{{ end }}

View file

@ -1,33 +0,0 @@
body {
background-color: #1e1c31;
text-align: center;
font-family: sans-serif;
font-weight: 600;
}
h1, h2{
color: white;
}
body .links {
max-width: 500px;
margin: auto;
text-align: center;
}
body .links p a {
display: block;
background-color: #296ae2;
color: Thistle;
text-decoration: none;
padding: 15px;
margin-bottom: 10px;
transition: 0.5s;
border-radius: 5px;
}
body .links p a:hover, a:active {
background-color: #719dee;
color: White;
transition: 0.5s;
}

BIN
web/static/gpg/public.asc (Stored with Git LFS)

Binary file not shown.

BIN
web/static/img/favicon.ico (Stored with Git LFS)

Binary file not shown.