package handlers // blog implementation concept import ( "bytes" "fmt" "html/template" "os" "path/filepath" "time" "git.tijl.dev/tijl/tijl.dev-core/modules/i18n" log "git.tijl.dev/tijl/tijl.dev-core/modules/logger" "git.tijl.dev/tijl/tijl.dev-core/modules/web" "github.com/gofiber/fiber/v2" "github.com/yuin/goldmark" "github.com/yuin/goldmark-meta" "github.com/yuin/goldmark/extension" "github.com/yuin/goldmark/parser" ) type Post struct { Meta PostMeta Content template.HTML } type PostMeta struct { Title string Slug string Description string Author string Language string PublishDate time.Time UpdatedDate time.Time Tags []string } var posts map[string]Post func LoadPosts() { posts = map[string]Post{} md := goldmark.New( goldmark.WithExtensions(extension.Table, extension.Strikethrough, extension.Linkify, extension.TaskList, extension.GFM, extension.DefinitionList, extension.Footnote, extension.Typographer, meta.Meta), goldmark.WithParserOptions( parser.WithAutoHeadingID(), ), ) files, err := filepath.Glob("blog/*.md") if err != nil { log.Error().Err(err).Msg("error reading folder") } for _, file := range files { content, err := os.ReadFile(file) if err != nil { log.Error().Err(err).Msg(" error reading file") continue } var buf bytes.Buffer context := parser.NewContext() if err := md.Convert(content, &buf, parser.WithContext(context)); err != nil { log.Fatal().Err(err) } metaData := meta.Get(context) postMeta, err := MapToPostMeta(metaData) if err != nil { log.Error().Err(err).Msg("error mapping interface to post meta") continue } posts[postMeta.Slug] = Post{ Content: template.HTML(buf.String()), Meta: postMeta, } } log.Debug().Msg("loaded blog posts") } func blogIndexHandler(c *fiber.Ctx) error { data := *web.Common(c) data["Title"] = i18n.Translate(c, "blog") data["Posts"] = posts return c.Render("blog", data, "layouts/base") } func blogHandler(c *fiber.Ctx) error { data := *web.Common(c) data["Title"] = i18n.Translate(c, "blog") post, ok := posts[c.Params("post")] if !ok { return c.Render("404", data, "layouts/base") } data["Post"] = post return c.Render("post", data, "layouts/base") } func MapToPostMeta(m map[string]interface{}) (PostMeta, error) { var post PostMeta var ok bool var err error if post.Title, ok = m["title"].(string); !ok { return post, fmt.Errorf("missing or invalid type for Title") } if post.Slug, ok = m["slug"].(string); !ok { return post, fmt.Errorf("missing or invalid type for Title") } if post.Description, ok = m["description"].(string); !ok { return post, fmt.Errorf("missing or invalid type for Description") } if post.Author, ok = m["author"].(string); !ok { return post, fmt.Errorf("missing or invalid type for Author") } if post.Language, ok = m["language"].(string); !ok { return post, fmt.Errorf("missing or invalid type for Language") } if publishDate, ok := m["publish_date"].(string); ok { post.PublishDate, err = time.Parse("02 Jan 2006", publishDate) if err != nil { return post, fmt.Errorf("invalid format for PublishDate: %v", err) } } else { return post, fmt.Errorf("missing or invalid type for PublishDate") } if updatedDate, ok := m["update_date"].(string); ok { post.UpdatedDate, err = time.Parse("02 Jan 2006", updatedDate) if err != nil { return post, fmt.Errorf("invalid format for UpdatedDate: %v", err) } } if tags, ok := m["tags"].([]interface{}); ok { for _, tag := range tags { if strTag, ok := tag.(string); ok { post.Tags = append(post.Tags, strTag) } else { return post, fmt.Errorf("invalid type in Tags slice") } } } return post, nil }