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 · 82 lines · Go Raw History
 1package main
 2
 3import (
 4	"encoding/json"
 5	"html/template"
 6)
 7
 8// personID matches the portfolio and the blog, so the sites describe one person
 9// rather than several with the same name.
10const (
11	personID  = "https://isaacbythewood.com/#person"
12	websiteID = baseURL + "/#website"
13	appID     = baseURL + "/#app"
14)
15
16// jsonLD marshals a graph for the script tag. template.JS is safe because
17// encoding/json escapes <, > and &, so no value can close the element early.
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
29// pageGraph is the graph every page on this site carries.
30func pageGraph(title, description, canonical string) template.JS {
31	return jsonLD(
32		map[string]any{
33			"@type":    "Person",
34			"@id":      personID,
35			"name":     authorName,
36			"url":      "https://isaacbythewood.com/",
37			"jobTitle": "Senior Solutions Architect",
38			"sameAs": []string{
39				"https://github.com/" + githubUser,
40				"https://isaacbythewood.com/",
41			},
42		},
43		map[string]any{
44			"@type":               "WebApplication",
45			"@id":                 appID,
46			"name":                "Analytics",
47			"url":                 baseURL + "/",
48			"description":         "Self-hosted website analytics. Page views, clicks, scrolls, sessions, and custom events.",
49			"applicationCategory": "BusinessApplication",
50			"operatingSystem":     "Any",
51			"author":              map[string]any{"@id": personID},
52			"publisher":           map[string]any{"@id": personID},
53			"image":               baseURL + "/static/og/card.png",
54			// A zero price is how schema.org says "free"; no offers means unknown.
55			"offers": map[string]any{
56				"@type":         "Offer",
57				"price":         "0",
58				"priceCurrency": "USD",
59			},
60		},
61		map[string]any{
62			"@type":       "WebSite",
63			"@id":         websiteID,
64			"url":         baseURL + "/",
65			"name":        siteName,
66			"description": "Self-hosted website analytics. Page views, clicks, scrolls, sessions, and custom events.",
67			"inLanguage":  "en",
68			"publisher":   map[string]any{"@id": personID},
69		},
70		map[string]any{
71			"@type":       "WebPage",
72			"@id":         canonical + "#webpage",
73			"url":         canonical,
74			"name":        title,
75			"description": description,
76			"isPartOf":    map[string]any{"@id": websiteID},
77			"about":       map[string]any{"@id": appID},
78			"inLanguage":  "en",
79		},
80	)
81}