repos
/ orchard main

orchard

mirror

Every site I host, in one repo, along with the Cloudflare Tunnel and Caddy that front them. It's all Go, Vite, and SQLite, and it runs on a desktop at home with nothing listening on an inbound port.

blogbuncaddycloudflare-tunneldockergogolanghomelabhtml-templatemonorepoself-hostedseosqlitestatic-sitetypstuptime-monitoringviteweb-analytics

2.5 KB · 101 lines · Go Raw History
  1package main
  2
  3import (
  4	"encoding/json"
  5	"html/template"
  6)
  7
  8// The Person node uses the same @id the blog's graph references, so the two
  9// sites describe one entity rather than two people with the same name.
 10
 11const (
 12	personID  = baseURL + "/#person"
 13	websiteID = baseURL + "/#website"
 14)
 15
 16// jsonLD marshals a graph for the script tag. template.JS is safe because
 17// encoding/json escapes <, > and & by default.
 18func jsonLD(nodes ...map[string]any) template.JS {
 19	buf, err := json.MarshalIndent(map[string]any{
 20		"@context": "https://schema.org",
 21		"@graph":   nodes,
 22	}, "", "  ")
 23	if err != nil {
 24		return ""
 25	}
 26	return template.JS(buf)
 27}
 28
 29func personNode() map[string]any {
 30	return map[string]any{
 31		"@type":    "Person",
 32		"@id":      personID,
 33		"name":     "Isaac Bythewood",
 34		"url":      baseURL + "/",
 35		"jobTitle": "Senior Solutions Architect",
 36		"email":    "mailto:" + contactMail,
 37		"image":    baseURL + "/static/og/card.png",
 38		"worksFor": map[string]any{
 39			"@type": "Organization",
 40			"name":  "Craftmaster Furniture",
 41		},
 42		"address": map[string]any{
 43			"@type":           "PostalAddress",
 44			"addressLocality": "Elkin",
 45			"addressRegion":   "NC",
 46			"addressCountry":  "US",
 47		},
 48		"sameAs": []string{
 49			"https://github.com/" + githubUser,
 50			"https://blog.bythewood.me/",
 51		},
 52	}
 53}
 54
 55func pageGraph(title, description, canonical string, crumbs []NavPage) template.JS {
 56	nodes := []map[string]any{
 57		personNode(),
 58		{
 59			"@type":       "WebSite",
 60			"@id":         websiteID,
 61			"url":         baseURL + "/",
 62			"name":        "Isaac Bythewood",
 63			"description": siteDesc,
 64			"inLanguage":  "en",
 65			"publisher":   map[string]any{"@id": personID},
 66		},
 67		{
 68			"@type":       "WebPage",
 69			"@id":         canonical + "#webpage",
 70			"url":         canonical,
 71			"name":        title,
 72			"description": description,
 73			"isPartOf":    map[string]any{"@id": websiteID},
 74			"about":       map[string]any{"@id": personID},
 75			"inLanguage":  "en",
 76		},
 77	}
 78
 79	// Below the root only. On the home page the trail would be one item
 80	// pointing at itself.
 81	if len(crumbs) > 0 {
 82		items := []map[string]any{
 83			{"@type": "ListItem", "position": 1, "name": "Home", "item": baseURL + "/"},
 84		}
 85		for i, c := range crumbs {
 86			items = append(items, map[string]any{
 87				"@type":    "ListItem",
 88				"position": i + 2,
 89				"name":     c.Title,
 90				"item":     baseURL + c.Href,
 91			})
 92		}
 93		nodes = append(nodes, map[string]any{
 94			"@type":           "BreadcrumbList",
 95			"itemListElement": items,
 96		})
 97	}
 98
 99	return jsonLD(nodes...)
100}