From 5dfd71bfe1c0e25da413f00256a302b13c88d26d Mon Sep 17 00:00:00 2001 From: Anhgelus Morhtuuzh Date: Thu, 2 Oct 2025 21:30:34 +0200 Subject: perf(backend): generate content during load --- backend/data.go | 4 +++ backend/logs.go | 66 ++++++++++++++++++++++++++++----------------- backend/router.go | 20 ++++++++++---- backend/templates/base.html | 11 +++----- 4 files changed, 65 insertions(+), 36 deletions(-) (limited to 'backend') diff --git a/backend/data.go b/backend/data.go index cd1ea9e..f3e8b42 100644 --- a/backend/data.go +++ b/backend/data.go @@ -89,3 +89,7 @@ func (d *data) Title() string { } return title } + +func (d *data) PubDate() string { + return "" +} diff --git a/backend/logs.go b/backend/logs.go index e25c638..d15be41 100644 --- a/backend/logs.go +++ b/backend/logs.go @@ -9,6 +9,7 @@ import ( "os" "path/filepath" "strings" + "sync" "git.anhgelus.world/anhgelus/small-world/markdown" "github.com/go-chi/chi/v5" @@ -16,22 +17,26 @@ import ( ) var ( - logs = map[string]string{} - loadedLogs = map[string]*logData{} + logs = map[string]*logData{} ) type logData struct { *data - LogTitle string `toml:"title"` - Description string `toml:"description"` - Img image `toml:"image"` - Content template.HTML `toml:"-"` + LogTitle string `toml:"title"` + Description string `toml:"description"` + Img image `toml:"image"` + pubDate toml.LocalDate `toml:"publication_date"` + Content template.HTML `toml:"-"` } func (d *logData) SetData(dt *data) { d.data = dt } +func (d *logData) PubDate() string { + return d.pubDate.String() +} + type image struct { Src string `toml:"src"` Alt string `toml:"alt"` @@ -62,6 +67,7 @@ func LoadLogs(cfg *Config) bool { } func readLogDir(path string, dir []os.DirEntry) error { + var wg sync.WaitGroup for _, d := range dir { p := filepath.Join(path, d.Name()) if d.IsDir() { @@ -76,18 +82,31 @@ func readLogDir(path string, dir []os.DirEntry) error { if !strings.HasSuffix(d.Name(), ".md") { return fmt.Errorf("file %s is not a markdown file", d.Name()) } - _, ok := logs[d.Name()] + slug := strings.TrimSuffix(p, ".md") + _, ok := logs[slug] if ok { return fmt.Errorf("log already exists: %s", d.Name()) } - logs[strings.TrimSuffix(d.Name(), ".md")] = p + dd := new(logData) + dd.data = new(data) + go func() { + wg.Add(1) + defer wg.Done() + ok = parseLog(dd, slug, strings.TrimSuffix(d.Name(), ".md")) + if ok { + slog.Debug("log parsed", "path", p) + } else { + slog.Debug("log skipped", "path", p) + } + }() } } + wg.Wait() return nil } func HandleLogs(r *chi.Mux) { - r.Route("/logs", func(r chi.Router) { + r.Route("/log", func(r chi.Router) { r.Get("/", handleLogList) r.Get("/{slug:[a-zA-Z0-9-]+}", handleLog) }) @@ -98,32 +117,30 @@ func handleLogList(w http.ResponseWriter, r *http.Request) { } func handleLog(w http.ResponseWriter, r *http.Request) { + cfg := r.Context().Value("config").(*Config) slug := chi.URLParam(r, "slug") - path, ok := logs[slug] - if !ok { - http.NotFoundHandler().ServeHTTP(w, r) - return - } - var d *logData - d, ok = loadedLogs[slug] + path := filepath.Join(cfg.LogFolder, slug) + d, ok := logs[path] if !ok { d = new(logData) d.data = new(data) - d.Article = true - d.LogTitle = slug - d.title = slug - if ok = parseLog(d, path); !ok { - w.WriteHeader(http.StatusInternalServerError) + if ok = parseLog(d, path, slug); !ok { + http.NotFoundHandler().ServeHTTP(w, r) return } - loadedLogs[slug] = d } d.handleGeneric(w, r, "log", d) } -func parseLog(d *logData, path string) bool { - b, err := os.ReadFile(path) +func parseLog(d *logData, path, slug string) bool { + d.Article = true + d.LogTitle = slug + d.title = slug + b, err := os.ReadFile(path + ".md") if err != nil { + if os.IsNotExist(err) { + return false + } panic(err) } var dd string @@ -147,5 +164,6 @@ func parseLog(d *logData, path string) bool { fmt.Println(errMd.Pretty()) return false } + logs[path] = d return true } diff --git a/backend/router.go b/backend/router.go index 374e28b..33c685d 100644 --- a/backend/router.go +++ b/backend/router.go @@ -20,28 +20,38 @@ const Version = "0.1.0" //go:embed templates var templates embed.FS -func NewRouter(debug bool, cfg *Config) *chi.Mux { +func SetupLogger(debug bool) { logFormat := httplog.SchemaECS.Concise(!debug) + logLevel := slog.LevelWarn + if debug { + logLevel = slog.LevelDebug + } + logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ ReplaceAttr: logFormat.ReplaceAttr, + Level: logLevel, })).With( slog.String("app", "anhgelus/small-web"), slog.String("version", Version), ) + slog.SetDefault(logger) +} + +func NewRouter(debug bool, cfg *Config) *chi.Mux { + r := chi.NewRouter() + logLevel := slog.LevelWarn if debug { logLevel = slog.LevelDebug } - r := chi.NewRouter() - r.Use(middleware.Timeout(30 * time.Second)) - r.Use(httplog.RequestLogger(logger, &httplog.Options{ + r.Use(httplog.RequestLogger(slog.Default(), &httplog.Options{ Level: logLevel, // Set log output to Elastic Common Schema (ECS) format. - Schema: logFormat, + Schema: httplog.SchemaECS.Concise(!debug), RecoverPanics: true, Skip: func(req *http.Request, respStatus int) bool { return respStatus == http.StatusNotFound || respStatus == http.StatusMethodNotAllowed diff --git a/backend/templates/base.html b/backend/templates/base.html index 34a7c7f..bef493f 100644 --- a/backend/templates/base.html +++ b/backend/templates/base.html @@ -1,5 +1,5 @@ - + @@ -15,6 +15,7 @@ + {{ if ne .PubDate "" }}{{ end }} @@ -31,13 +32,9 @@ {{ if .Article }} -
- {{ template "body" . }} -
+
{{ template "body" . }}
{{ else }} -
- {{ template "body" . }} -
+
{{ template "body" . }}
{{ end }}