Files
b1tsblog/cmd/blog/main.go
jbergner dce90ca88b
All checks were successful
release-tag / release-image (push) Successful in 2m20s
bugfix
2025-05-11 16:56:08 +02:00

406 lines
9.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"encoding/json"
"fmt"
"html/template"
"io"
"net/http"
"os"
"os/exec"
"os/signal"
"path/filepath"
"strconv"
"strings"
"syscall"
"time"
"git.send.nrw/sendnrw/b1tsblog/internal/article"
)
var TickCatalog []TickEntry
func SaveTickCatalog(filename string) error {
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
encoder := json.NewEncoder(file)
encoder.SetIndent("", " ")
return encoder.Encode(TickCatalog)
}
func LoadTickCatalog(filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()
return json.NewDecoder(file).Decode(&TickCatalog)
}
func IncTick(xSlug string) error {
for a, b := range TickCatalog {
if b.Slug == xSlug {
TickCatalog[a].Count = TickCatalog[a].Count + 1
return nil
}
}
newEntry := TickEntry{Slug: xSlug, Count: 1}
TickCatalog = append(TickCatalog, newEntry)
return fmt.Errorf("")
}
func getTick(xSlug string) string {
for _, b := range TickCatalog {
if b.Slug == xSlug {
var n int64 = b.Count
return strconv.FormatInt(n, 10)
}
}
return "0"
}
type TickEntry struct {
Slug string `json:"slug"`
Count int64 `json:"count"`
}
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
)
var Xarticles []article.Article
func main() {
// Signal-Kanal einrichten
stop := make(chan os.Signal, 1)
signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM)
// Goroutine, die auf Signale wartet
go func() {
<-stop
fmt.Println("Stop Sign...")
prepareExit()
os.Exit(0)
}()
// --- Verzeichnisse konfigurierbar machen -------------------------
contentDir := getenv("BLOG_CONTENT_DIR", "/content")
staticDir := getenv("BLOG_STATIC_DIR", "/app/internal/web/static")
pagesDir := getenv("BLOG_PAGES_DIR", "/pages")
templatesDir := getenv("BLOG_TEMPLATES_DIR", "/templates")
ticksDir := getenv("BLOG_TICKS_DIR", "/ticks")
gitEnable := enabled("GIT_ENABLE", false)
gitRepo := getenv("GIT_REPO", "null")
gitBranch := getenv("GIT_BRANCH", "main")
gitDir := getenv("GIT_DIR", "/git-temp")
gitInterval := getenv("GIT_INTERVAL", "10")
TickCatalog = nil
if err := LoadTickCatalog(ticksDir + "/ticks.json"); err != nil {
fmt.Println("Fehler beim Laden:", err)
}
fmt.Println("Geladener Katalog:", TickCatalog)
funcs := template.FuncMap{
"now": time.Now, // jetztZeit bereitstellen
}
// Basislayout zuerst parsen
layout := template.Must(template.New("base").Funcs(funcs).ParseFiles(templatesDir + "/base.html"))
// LISTSeite: base + list.html
tplList = template.Must(layout.Clone())
template.Must(tplList.Funcs(funcs).ParseFiles(templatesDir + "/list.html"))
// ARTICLEInstanz
tplArticle = template.Must(layout.Clone())
template.Must(tplArticle.Funcs(funcs).ParseFiles(templatesDir + "/article.html"))
tplPage := template.Must(layout.Clone())
template.Must(tplPage.ParseFiles(templatesDir + "/page.html"))
mux := http.NewServeMux()
articles, err := article.LoadDir(contentDir)
if err != nil {
fmt.Println(err)
}
Xarticles = articles
staticPages, err := article.LoadStatic(pagesDir)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(staticPages)
}
// Handler für /
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
/* */
for a, b := range Xarticles {
Xarticles[a].Counter = getTick(b.Slug)
}
/* */
if err := tplList.ExecuteTemplate(w, "layout", article.ListPage{
Title: "Startseite",
Description: "Alle Artikel im Überblick",
Articles: Xarticles,
}); 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 Xarticles {
if a.Slug == slug {
IncTick(slug)
t := getTick(slug)
a.Counter = t
if err := tplArticle.ExecuteTemplate(w, "layout", a); err != nil {
http.Error(w, err.Error(), 500)
}
return
}
}
http.NotFound(w, r)
})
mux.HandleFunc("/page/", func(w http.ResponseWriter, r *http.Request) {
slug := strings.TrimPrefix(r.URL.Path, "/page/")
p, ok := staticPages[slug]
if !ok {
http.NotFound(w, r)
return
}
// "layout" kommt aus deinem TemplatePool (list/article nutzen es ja auch)
if err := tplPage.ExecuteTemplate(w, "page", p); err != nil {
http.Error(w, err.Error(), 500)
}
})
mux.Handle("/static/", cacheControl(http.StripPrefix("/static/", http.FileServer(http.Dir(staticDir)))))
if gitEnable {
xMinute, _ := strconv.Atoi(gitInterval)
xDuration := time.Duration(xMinute) * time.Minute
go startAutoClone(gitRepo, gitBranch, gitDir, xDuration)
}
StopServer(http.ListenAndServe(":8080", mux))
}
func prepareExit() {
fmt.Println("~", "Running exit tasks...")
if err := SaveTickCatalog(getenv("BLOG_TICKS_DIR", "/ticks") + "/ticks.json"); err != nil {
fmt.Println("Fehler beim Speichern:", err)
}
fmt.Println("Geladener Katalog:", TickCatalog)
fmt.Println("~", "Exit completed.")
}
func StopServer(e error) {
fmt.Println("~", "Stopping server...")
prepareExit()
fmt.Println("~", "Server stopped!")
}
func cloneRepo(repoURL, branch, dir string) {
fmt.Printf("Starte Klonvorgang für Branch '%s'...\n", branch)
// Verzeichnis löschen
if err := os.RemoveAll(dir); err != nil {
fmt.Println("Fehler beim Löschen des Verzeichnisses:", err)
return
}
// Git-Clone mit Branch
cmd := exec.Command("git", "clone", "--branch", branch, "--single-branch", repoURL, dir)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
fmt.Println("Fehler beim Klonen:", err)
} else {
fmt.Println("Repo erfolgreich geklont.")
}
contentDir := getenv("BLOG_CONTENT_DIR", "/content")
articles, err := article.LoadDir(contentDir)
if err != nil {
fmt.Println(err)
}
err = os.RemoveAll("/content")
if err != nil {
fmt.Println(err, "/content")
}
err = os.RemoveAll("/static")
if err != nil {
fmt.Println(err, "/static")
}
err = os.RemoveAll("/pages")
if err != nil {
fmt.Println(err, "/pages")
}
err = os.RemoveAll("/templates")
if err != nil {
fmt.Println(err, "/templates")
}
if err := os.MkdirAll("/content", 0755); err != nil {
fmt.Println("Fehler beim Erstellen des Zielordners:", err)
return
}
if err := os.MkdirAll("/static", 0755); err != nil {
fmt.Println("Fehler beim Erstellen des Zielordners:", err)
return
}
if err := os.MkdirAll("/pages", 0755); err != nil {
fmt.Println("Fehler beim Erstellen des Zielordners:", err)
return
}
if err := os.MkdirAll("/templates", 0755); err != nil {
fmt.Println("Fehler beim Erstellen des Zielordners:", err)
return
}
if err := copyDirContents("/git-temp/articles", "/content"); err != nil {
fmt.Println("Fehler beim Kopieren:", err)
} else {
fmt.Println("Kopieren abgeschlossen.")
}
if err := copyDirContents("/git-temp/static", "/static"); err != nil {
fmt.Println("Fehler beim Kopieren:", err)
} else {
fmt.Println("Kopieren abgeschlossen.")
}
if err := copyDirContents("/git-temp/pages", "/pages"); err != nil {
fmt.Println("Fehler beim Kopieren:", err)
} else {
fmt.Println("Kopieren abgeschlossen.")
}
if err := copyDirContents("/git-temp/templates", "/templates"); err != nil {
fmt.Println("Fehler beim Kopieren:", err)
} else {
fmt.Println("Kopieren abgeschlossen.")
}
Xarticles = articles
}
func copyDirContents(srcDir, destDir string) error {
entries, err := os.ReadDir(srcDir)
if err != nil {
return fmt.Errorf("Fehler beim Lesen von %s: %w", srcDir, err)
}
for _, entry := range entries {
srcPath := filepath.Join(srcDir, entry.Name())
destPath := filepath.Join(destDir, entry.Name())
info, err := entry.Info()
if err != nil {
return err
}
if info.IsDir() {
if err := os.MkdirAll(destPath, info.Mode()); err != nil {
return fmt.Errorf("Fehler beim Erstellen von Ordner %s: %w", destPath, err)
}
// rekursiv kopieren
if err := copyDirContents(srcPath, destPath); err != nil {
return err
}
} else {
if err := copyFile(srcPath, destPath, info); err != nil {
return err
}
}
}
return nil
}
func copyFile(src, dest string, info os.FileInfo) error {
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
out, err := os.Create(dest)
if err != nil {
return err
}
defer out.Close()
if _, err := io.Copy(out, in); err != nil {
return err
}
return os.Chmod(dest, info.Mode())
}
func startAutoClone(repoURL, branch, dir string, interval time.Duration) {
go cloneRepo(repoURL, branch, dir) // sofortiger Start
ticker := time.NewTicker(interval)
defer ticker.Stop()
for range ticker.C {
go cloneRepo(repoURL, branch, dir)
}
}