it works!

initial build that basically replicates my old HTML site
This commit is contained in:
Lilian Jónsdóttir 2024-01-22 20:10:14 -08:00
parent 0d809bfea8
commit a7f6bb35e7
17 changed files with 614 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
bin/

31
cmd/web/main.go Normal file
View file

@ -0,0 +1,31 @@
// Main entry point for the web app. Does all
// the setup, then runs the http server.
package main
import (
"fmt"
"net/http"
"git.burning.moe/celediel/burning.moe/internal/config"
"git.burning.moe/celediel/burning.moe/internal/handlers"
"git.burning.moe/celediel/burning.moe/internal/render"
)
func main() {
// Initialise app and config
app := config.Initialise()
// Initialise handlers and renderer
handlers.Initialise(&app)
render.Initialise(&app)
// Initialise the webserver
srv := &http.Server{
Addr: fmt.Sprintf(":%d", app.ListenPort),
Handler: routes(&app),
}
// and finally, start the server
app.Logger.Printf("Listening on port %d", app.ListenPort)
srv.ListenAndServe()
}

31
cmd/web/routes.go Normal file
View file

@ -0,0 +1,31 @@
package main
import (
"net/http"
"git.burning.moe/celediel/burning.moe/internal/config"
"git.burning.moe/celediel/burning.moe/internal/handlers"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)
// routes handles all of the HTTP setup. Middleware is enabled,
// static fileserver is setup, and handlers are ... handled
func routes(app *config.AppConfig) http.Handler {
mux := chi.NewRouter()
// Import some middleware
mux.Use(middleware.Recoverer)
// Setup static file server
app.Logger.Debug("Setting up /static file server")
mux.Handle("/static/*", http.StripPrefix("/static", http.FileServer(http.Dir("./static"))))
// Setup routes for handlers
for _, handler := range handlers.Handlers {
app.Logger.Info("Setting up handler for " + handler.Handles)
mux.Get(handler.Handles, handler.Handler)
}
return mux
}

25
go.mod
View file

@ -1,3 +1,28 @@
module git.burning.moe/celediel/burning.moe module git.burning.moe/celediel/burning.moe
go 1.21.5 go 1.21.5
require (
github.com/go-chi/chi/v5 v5.0.11
github.com/ilyakaznacheev/cleanenv v1.5.0
github.com/magefile/mage v1.15.0
)
require (
github.com/BurntSushi/toml v1.3.2 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/lipgloss v0.9.1 // indirect
github.com/charmbracelet/log v0.3.1 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.18 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
golang.org/x/sys v0.13.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 // indirect
)

43
go.sum Normal file
View file

@ -0,0 +1,43 @@
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg=
github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I=
github.com/charmbracelet/log v0.3.1 h1:TjuY4OBNbxmHWSwO3tosgqs5I3biyY8sQPny/eCMTYw=
github.com/charmbracelet/log v0.3.1/go.mod h1:OR4E1hutLsax3ZKpXbgUqPtTjQfrh1pG3zwHGWuuq8g=
github.com/go-chi/chi/v5 v5.0.11 h1:BnpYbFZ3T3S1WMpD79r7R5ThWX40TaFB7L31Y8xqSwA=
github.com/go-chi/chi/v5 v5.0.11/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/ilyakaznacheev/cleanenv v1.5.0 h1:0VNZXggJE2OYdXE87bfSSwGxeiGt9moSR2lOrsHHvr4=
github.com/ilyakaznacheev/cleanenv v1.5.0/go.mod h1:a5aDzaJrLCQZsazHol1w8InnDcOX0OColm64SlIi6gk=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 h1:slmdOY3vp8a7KQbHkL+FLbvbkgMqmXojpFUO/jENuqQ=
olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3/go.mod h1:oVgVk4OWVDi43qWBEyGhXgYxt7+ED4iYNpTngSLX2Iw=

83
internal/config/config.go Normal file
View file

@ -0,0 +1,83 @@
package config
import (
"math"
"os"
"git.burning.moe/celediel/burning.moe/internal/models"
"github.com/charmbracelet/log"
"github.com/ilyakaznacheev/cleanenv"
)
// AppConfig contains data to be accessed across the app.
type AppConfig struct {
ListenPort uint16
TemplateCache models.TemplateCache
UseCache bool
Logger *log.Logger
LogLevel log.Level
}
// defaluts contains default settings that are used if no environmental variables are set
var defaults = &AppConfig{
ListenPort: 9001,
UseCache: true,
LogLevel: log.InfoLevel,
}
// ConfigDatabase contains data to be loaded from environmental variables
type ConfigDatabase struct {
Port uint16 `env:"PORT" env-default:"9001" env-description:"server port"`
LogLevel string `env:"LOGLEVEL" env-default:"warn" env-description:"Logging level. Default: warn, Possible values: debug info warn error fatal none"`
UseCache bool `env:"CACHE" env-default:"true" env-description:"Use template cache"`
}
// Initialises the app wide AppConfig, loads values from environment, and set up the Logger
func Initialise() AppConfig {
app := *defaults
app.Logger = log.New(os.Stderr)
// load values from config
if cfg, err := loadConfig(); err == nil {
app.ListenPort = cfg.Port
app.UseCache = cfg.UseCache
app.LogLevel = logLevelFromString(cfg.LogLevel)
} else {
app.Logger.Print("Failed loading config from environment", "err", err)
}
app.Logger.SetLevel(app.LogLevel)
app.Logger.Debug("Loaded config from environment:", "port", app.ListenPort, "useCache", app.UseCache, "log_level", app.LogLevel)
return app
}
// loadConfig utilises cleanenv to load config values from the environment
func loadConfig() (ConfigDatabase, error) {
var cfg ConfigDatabase
if err := cleanenv.ReadEnv(&cfg); err != nil {
return ConfigDatabase{}, err
} else {
return cfg, nil
}
}
// logLevelFromString turns a string like "warn" into a log.Level like log.WarnLevel
func logLevelFromString(level string) log.Level {
switch level {
case "debug":
return log.DebugLevel
case "info":
return log.InfoLevel
case "warn":
return log.WarnLevel
case "error":
return log.ErrorLevel
case "fatal":
return log.FatalLevel
case "none":
return math.MaxInt32
default:
return defaults.LogLevel
}
}

View file

@ -0,0 +1,42 @@
package handlers
import (
"net/http"
"git.burning.moe/celediel/burning.moe/internal/config"
"git.burning.moe/celediel/burning.moe/internal/models"
"git.burning.moe/celediel/burning.moe/internal/render"
)
// Handler holds data required for handlers.
type Handler struct {
Handles string
Handler func(w http.ResponseWriter, r *http.Request)
}
var app *config.AppConfig
// The actual handlers
var Handlers = []Handler{
// /about
{
Handles: "/about",
Handler: func(w http.ResponseWriter, r *http.Request) {
app.Logger.Info("Got request for about page.")
render.RenderTemplate(w, "about.page", &models.TemplateData{})
},
},
// / comes last
{
Handles: "/",
Handler: func(w http.ResponseWriter, r *http.Request) {
app.Logger.Info("Got request for homepage.")
render.RenderTemplate(w, "home.page", &models.TemplateData{})
},
},
}
// Initialise the handlers package.
func Initialise(a *config.AppConfig) {
app = a
}

View file

@ -0,0 +1,18 @@
package models
import (
"html/template"
"time"
)
// TemplateCache holds the template cache as map of TemplateCacheItem
type TemplateCache struct {
Cache map[string]TemplateCacheItem
}
// TemplateCacheItem holds a pointer to a generated
// template, and the time it was generated at.
type TemplateCacheItem struct {
Template *template.Template
GeneratedAt time.Time
}

View file

@ -0,0 +1,14 @@
package models
// TemplateData holds data sent from handlers to templates.
type TemplateData struct {
StringMap map[string]string
IntMap map[string]int
FloatMap map[string]float32
Data map[string]interface{}
CSRFToken string
Flash string
Warning string
Error string
}

111
internal/render/render.go Normal file
View file

@ -0,0 +1,111 @@
package render
import (
"bytes"
"fmt"
"html/template"
"net/http"
"path/filepath"
"time"
"git.burning.moe/celediel/burning.moe/internal/config"
"git.burning.moe/celediel/burning.moe/internal/models"
)
const (
templatesDir string = "./templates/"
layoutGlob string = "*.layout.tmpl"
pageGlob string = "*.page.tmpl"
)
var app *config.AppConfig
// Initialise the render package.
func Initialise(a *config.AppConfig) {
var err error
app = a
app.TemplateCache, err = GenerateNewTemplateCache()
if err != nil {
app.Logger.Fatal("Error generating template cache, bailing out!")
}
}
// GenerateNewTemplateCache generates a new template cache.
func GenerateNewTemplateCache() (models.TemplateCache, error) {
// start with an empty map
cache := models.TemplateCache{}
cache.Cache = map[string]models.TemplateCacheItem{}
// Generate a list of pages based on globs
pages, err := filepath.Glob(templatesDir + pageGlob)
// a nice try catch would be pretty cool right about here
if err != nil {
return cache, err
}
// Iterate each page, parsing the file and adding it to the cache
for _, page := range pages {
name := filepath.Base(page)
app.Logger.Info("Generating template " + name)
templateSet, err := template.New(name).ParseFiles(page)
if err != nil {
return cache, err
}
// Glob and parse any layouts found
layouts, err := filepath.Glob(templatesDir + layoutGlob)
if err != nil {
return cache, err
}
if len(layouts) > 0 {
templateSet, err = templateSet.ParseGlob(templatesDir + layoutGlob)
if err != nil {
return cache, err
}
}
cache.Cache[name] = models.TemplateCacheItem{
Template: templateSet,
GeneratedAt: time.Now(),
}
}
// All was good, so return the cache, and no error
return cache, nil
}
// RenderTemplate renders requested template (t), pulling from cache.
func RenderTemplate(w http.ResponseWriter, t string, data *models.TemplateData) {
filename := t + ".tmpl"
var cache models.TemplateCache
if app.UseCache {
cache = app.TemplateCache
} else {
var err error
cache, err = GenerateNewTemplateCache()
if err != nil {
app.Logger.Fatal("Error generating template cache, bailing out!")
}
}
// Get templates from cache
template, ok := cache.Cache[filename]
if !ok {
app.Logger.Fatal(fmt.Sprintf("Couldn't get %s from template cache, bailing out!", filename))
}
// Execute templates in a new buffer
buf := new(bytes.Buffer)
err := template.Template.Execute(buf, data)
if err != nil {
app.Logger.Fatal("Error executing template %s! Goodbye!", "err", err)
}
_, err = buf.WriteTo(w)
if err != nil {
app.Logger.Error("Error writing template %s!\n", "err", err)
}
}

38
magefile.go Normal file
View file

@ -0,0 +1,38 @@
//go:build mage
package main
import (
"fmt"
"os"
"github.com/magefile/mage/sh"
)
var (
binaryName string = "burningmoe"
buildDir string = "bin"
cmd string = fmt.Sprintf(".%[1]ccmd%[1]cweb", os.PathSeparator)
output string = fmt.Sprintf(".%[1]c%[2]s%[1]c%[3]s", os.PathSeparator, buildDir, binaryName)
)
func Build() error {
fmt.Println("Building...")
return sh.Run("go", "build", "-o", output, cmd)
}
func Run() error {
fmt.Println("Running...")
return sh.Run("go", "run", cmd)
}
func RunBinary() error {
Build()
fmt.Println("Running binary...")
return sh.Run(output)
}
func Clean() error {
fmt.Println("Cleaning...")
return os.Remove(output)
}

71
static/css/style.css Normal file
View file

@ -0,0 +1,71 @@
@import url('https://fonts.googleapis.com/css2?family=Gloria+Hallelujah&display=swap');
body {
background: #FFF;
color: #121212;
text-align: center;
font-family: 'Gloria Hallelujah', cursive;
}
a {
color: #333;
text-decoration: none;
-o-transition: .5s;
-ms-transition: .5s;
-moz-transition: .5s;
-webkit-transition: .5s;
/* ...and now for the proper property */
transition: .5s;
}
a:hover {
color: #999;
}
a.back {
color: #111;
}
#stuff {
margin: 55px 150px 25px 150px;
}
#words {
font-size: 1.0em;
margin-top: -10px;
}
#bigwords {
font-size: 1.3em;
}
#leftfooter {
position: fixed;
bottom: 10px;
left: 10px;
font-size: 0.75em;
text-align: left;
}
#rightfooter {
position: fixed;
bottom: 10px;
right: 10px;
font-size: 0.75em;
text-align: right;
}
#header {
margin: 0px;
margin-top: -20px;
}
h1 {
font-size: 1.7em;
font-family: "ヒラギノ角ゴ Pro W3", "Hiragino Kaku Gothic Pro", Osaka, "メイリオ", Meiryo, " Pゴシック", "MS PGothic", sans-serif;
}
h2 {
padding-top: 0px;
font-size: 1.4em;
}

BIN
static/img/burningmoe.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 KiB

BIN
static/img/moemoe.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

53
templates/about.page.tmpl Normal file
View file

@ -0,0 +1,53 @@
{{- template "base" . -}}
{{- define "content" }}
<span id="words">
<h4>celediel</h4>
<span id="bigwords">
she/her, 1989, queer anarchist, self-taught aspiring developer
</span>
<h4>links</h4>
<!-- personal links -->
<a href="https://matrix.to/#/@celediel:burning.moe">
<span class="iconify" data-icon="tabler:brand-matrix"></span>
matrix
</a><br />
<a href="https://slrpnk.net/u/Celediel">
<span class="iconify" data-icon="simple-icons:lemmy"></span>
lemmy
</a><br />
<a href="https://git.burning.moe/celediel">
<span class="iconify" data-icon="mdi:git"></span>
self-hosted git
</a><br />
<a href="https://github.com/celediel">
<span class="iconify" data-icon="bi:github"></span>
github
</a><br />
<!-- web app links -->
<h4>hosted apps</h4>
<a href="https://git.burning.moe/">
<span class="iconify" data-icon="mdi:git"></span>
self-hosted git
</a><br />
<a href="https://bin.burning.moe/">
<span class="iconify" data-icon="fluent:bin-recycle-20-filled"></span>
wastebin
</a><br />
<a href="https://gist.burning.moe/">
<span class="iconify" data-icon="carbon:paste"></span>
gist
</a>
<br /><br />
<a href="/">back</a>
</span>
{{ end -}}
{{- define "js" }}
<script src="https://code.iconify.design/3/3.1.0/iconify.min.js"></script>
{{ end -}}

View file

@ -0,0 +1,37 @@
{{- define "base" -}}
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>burning.moe</title>
<link rel="stylesheet" href="static/css/style.css">
{{- block "css" . }}
{{- end }}
</head>
<body>
<div id="stuff">
{{- block "content" . }}
{{ end -}}
</div>
{{- block "js" . }}
{{ end -}}
<div id="leftfooter">
note: please do not set fire to cute girls
</div>
<div id="rightfooter">
powered by
<a href="https://www.debian.org/">debian</a>
and
<a href="https://go.dev/">go</a>,
proxied by
<a href="https://caddyserver.com/">caddy</a>
-
<a href="https://git.burning.moe/celediel/burning.moe">source</a>
</div>
</body>
</html>
{{ end -}}

16
templates/home.page.tmpl Normal file
View file

@ -0,0 +1,16 @@
{{- template "base" . -}}
{{- define "content" }}
<img src="static/img/moemoe.png">
<div id="header">
<!-- <h1>燃え 萌え</h1> -->
<h2>burning.moe</h2>
</div>
<a href="http://www.pixiv.net/member_illust.php?mode=medium&illust_id=22647957">
<img src="static/img/burningmoe.jpg" style="max-width:400px;" />
</a>
<br />
<span id="words">
<a href="about">about</a>
</span>
{{ end -}}