From 92d272b36e5e367cbc58f4a3bfd71bd3443aeecc Mon Sep 17 00:00:00 2001 From: jbergner Date: Sun, 4 May 2025 13:40:31 +0200 Subject: [PATCH] Passt so --- Dockerfile | 19 ++++++ cmd/blog/main.go | 100 ++++++++++++++++++++++++++++ content/2025/test.md | 5 ++ go.mod | 5 ++ go.sum | 2 + internal/article/load.go | 97 +++++++++++++++++++++++++++ internal/article/model.go | 14 ++++ internal/web/templates/article.html | 20 ++++++ internal/web/templates/base.html | 15 +++++ internal/web/templates/list.html | 16 +++++ 10 files changed, 293 insertions(+) create mode 100644 Dockerfile create mode 100644 cmd/blog/main.go create mode 100644 content/2025/test.md create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/article/load.go create mode 100644 internal/article/model.go create mode 100644 internal/web/templates/article.html create mode 100644 internal/web/templates/base.html create mode 100644 internal/web/templates/list.html diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..9593caf --- /dev/null +++ b/Dockerfile @@ -0,0 +1,19 @@ +# --- Build step --- + FROM golang:1.22 AS build + WORKDIR /app + COPY go.mod go.sum ./ + RUN go mod download + COPY . . + RUN CGO_ENABLED=0 go build -o /blog ./cmd/blog + + # --- Runtime + optional MySQL client libraries --- + FROM debian:bookworm-slim + RUN apt-get update && apt-get install -y default-mysql-client ca-certificates && rm -rf /var/lib/apt/lists/* + COPY --from=build /blog /usr/local/bin/blog + COPY content/ /content + COPY internal/web/static/ /static + EXPOSE 8080 + ENV BLOG_CONTENT_DIR=/content + ENV BLOG_STATIC_DIR=/static + CMD ["blog"] + \ No newline at end of file diff --git a/cmd/blog/main.go b/cmd/blog/main.go new file mode 100644 index 0000000..0167ed5 --- /dev/null +++ b/cmd/blog/main.go @@ -0,0 +1,100 @@ +package main + +import ( + "fmt" + "html/template" + "net/http" + "os" + "strconv" + "strings" + "time" + + "git.send.nrw/sendnrw/b1tsblog/internal/article" +) + +func getenv(k, d string) string { + if v := os.Getenv(k); v != "" { + return v + } + return d +} + +func enabled(k string, def bool) bool { + b, err := strconv.ParseBool(strings.ToLower(os.Getenv(k))) + if err != nil { + return def + } + return b +} + +func cacheControl(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Cache-Control", "public, max-age=31536000, immutable") + next.ServeHTTP(w, r) + }) +} + +var ( + tplList *template.Template + tplArticle *template.Template +) + +func main() { + + funcs := template.FuncMap{ + "now": time.Now, // jetzt‑Zeit bereitstellen + } + + // Basislayout zuerst parsen + base := template.Must( + template.New("base").Funcs(funcs). + ParseFiles("internal/web/templates/base.html"), + ) + + // LIST‑Seite: base + list.html + tplList = template.Must(base.Clone()) + template.Must(tplList.Funcs(funcs).ParseFiles("internal/web/templates/list.html")) + + // ARTICLE‑Instanz + tplArticle = template.Must(base.Clone()) + template.Must(tplArticle.Funcs(funcs).ParseFiles("internal/web/templates/article.html")) + + mux := http.NewServeMux() + + staticDir := "internal/web/static" + + articles, err := article.LoadDir("content") + if err != nil { + fmt.Println(err) + } + + // Handler für / + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/" { + http.NotFound(w, r) + return + } + if err := tplList.ExecuteTemplate(w, "base", articles); err != nil { + http.Error(w, err.Error(), 500) + } + }) + + mux.HandleFunc("/post/", func(w http.ResponseWriter, r *http.Request) { + slug := strings.TrimPrefix(r.URL.Path, "/post/") + for _, a := range articles { + if a.Slug == slug { + if err := tplArticle.ExecuteTemplate(w, "base", a); err != nil { + http.Error(w, err.Error(), 500) + } + return + } + } + http.NotFound(w, r) + }) + + mux.Handle("/static/", + cacheControl(http.StripPrefix("/static/", + http.FileServer(http.Dir(staticDir))))) + + http.ListenAndServe(":8080", mux) +} diff --git a/content/2025/test.md b/content/2025/test.md new file mode 100644 index 0000000..6d23997 --- /dev/null +++ b/content/2025/test.md @@ -0,0 +1,5 @@ + + +# Hello Markdown 🌟 + +Dies ist *kursiv*, **fett** und ein [Link](https://example.com). diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..7eb502e --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module git.send.nrw/sendnrw/b1tsblog + +go 1.23.1 + +require github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..148941c --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b h1:EY/KpStFl60qA17CptGXhwfZ+k1sFNJIUNR8DdbcuUk= +github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= diff --git a/internal/article/load.go b/internal/article/load.go new file mode 100644 index 0000000..f4a24d5 --- /dev/null +++ b/internal/article/load.go @@ -0,0 +1,97 @@ +// internal/article/load.go +package article + +// internal/article/load.go (gekürzt) + +import ( + "bufio" + "encoding/json" + "fmt" + "html/template" + "io" + "io/fs" + "os" + "path/filepath" + "sort" + "strings" + "time" + + md "github.com/gomarkdown/markdown" + "github.com/gomarkdown/markdown/html" + "github.com/gomarkdown/markdown/parser" +) + +func LoadDir(root string) ([]Article, error) { + var out []Article + + // gültige Extension‑Maske + exts := parser.CommonExtensions | parser.AutoHeadingIDs | parser.DefinitionLists + mdParser := parser.NewWithExtensions(exts) + mdRenderer := html.NewRenderer(html.RendererOptions{ + Flags: html.CommonFlags | html.HrefTargetBlank, + }) + + err := filepath.WalkDir(root, func(p string, d fs.DirEntry, err error) error { + if err != nil || d.IsDir() { + return err + } + + ext := filepath.Ext(p) + if ext != ".html" && ext != ".md" { + return nil + } + + f, err := os.Open(p) + if err != nil { + return err + } + defer f.Close() + + r := bufio.NewReader(f) + headerLine, err := r.ReadBytes('\n') + if err != nil && err != io.EOF { + return err + } + + var meta struct { + Title string `json:"title"` + Date string `json:"date"` + Slug string `json:"slug"` + } + clean := strings.TrimSpace(string(headerLine)) + + clean = strings.TrimPrefix(clean, "" abschneiden (egal ob Leerzeichen davor) + if idx := strings.Index(clean, "-->"); idx != -1 { + clean = clean[:idx] + } + clean = strings.TrimSpace(clean) + + if err := json.Unmarshal([]byte(clean), &meta); err != nil { + return fmt.Errorf("Front‑Matter in %s: %w (%q)", p, err, clean) + } + + bodyBytes, err := io.ReadAll(r) + if err != nil { + return err + } + + htmlBody := bodyBytes + if ext == ".md" { + htmlBody = md.ToHTML(bodyBytes, mdParser, mdRenderer) + } + + date, _ := time.Parse("2006-01-02", meta.Date) + out = append(out, Article{ + Title: meta.Title, + Slug: meta.Slug, + Date: date, + Body: template.HTML(htmlBody), + }) + return nil + }) + sort.Slice(out, func(i, j int) bool { return out[i].Date.After(out[j].Date) }) + return out, err +} diff --git a/internal/article/model.go b/internal/article/model.go new file mode 100644 index 0000000..b11a434 --- /dev/null +++ b/internal/article/model.go @@ -0,0 +1,14 @@ +// internal/article/model.go +package article + +import ( + "html/template" + "time" +) + +type Article struct { + Title string + Slug string + Date time.Time + Body template.HTML // already trusted +} diff --git a/internal/web/templates/article.html b/internal/web/templates/article.html new file mode 100644 index 0000000..b0834e2 --- /dev/null +++ b/internal/web/templates/article.html @@ -0,0 +1,20 @@ +{{ define "article" }} + {{ template "base.html" . }} +{{ end }} + +{{ define "title" }}{{ .Title }} – B1tsblog{{ end }} + +{{ define "body" }} +
+
+

{{ .Title }}

+ +
+ +
+ {{ .Body }} +
+ +

← Alle Artikel

+
+{{ end }} diff --git a/internal/web/templates/base.html b/internal/web/templates/base.html new file mode 100644 index 0000000..3188e7e --- /dev/null +++ b/internal/web/templates/base.html @@ -0,0 +1,15 @@ + + + + + {{ block "title" . }}B1tsblog{{ end }} + + + + + + +

B1tsblog

+
{{ block "body" . }}{{ end }}
+ + diff --git a/internal/web/templates/list.html b/internal/web/templates/list.html new file mode 100644 index 0000000..18417d5 --- /dev/null +++ b/internal/web/templates/list.html @@ -0,0 +1,16 @@ +{{ define "list" }} {{/* ‑‑ ausführbarer Entry‑Point */}} + {{ template "base.html" . }} {{/* ruft das Layout auf */}} +{{ end }} + +{{ define "title" }}Alle Artikel{{ end }} + +{{ define "body" }} + +{{ end }}