package handlers import ( "bytes" "fmt" "html/template" "strings" "time" "github.com/SayaAndy/saya-today-web/internal/blog" "github.com/SayaAndy/saya-today-web/internal/frontmatter" "github.com/SayaAndy/saya-today-web/internal/router" "github.com/gofiber/fiber/v2" "github.com/yuin/goldmark" ) func init() { router.Routes = append(router.Routes, &BlogPageHandler{}) } type BlogPageHandler struct { router.BasicHandler } func (r *BlogPageHandler) Filter() (method string, path string) { return "GET", "/:lang/blog/:title" } func (r *BlogPageHandler) IsTemplated() bool { return true } func (r *BlogPageHandler) TemplatesToInject() []string { return []string{"views/pages/blog-page.html"} } func (r *BlogPageHandler) ToCache() router.CacheSetting { return router.ByUrlOnly } func (r *BlogPageHandler) CacheDuration() time.Duration { return 15 * time.Minute } func (r *BlogPageHandler) ToValidateLang() router.LangSetting { return router.InPath } func (r *BlogPageHandler) SitemapInfo(supplements *router.Supplements) []router.SitemapInfo { sitemapInfo := []router.SitemapInfo{} pages, err := supplements.BlogClient.Scan("") if err != nil { return sitemapInfo } for _, page := range pages { sitemapInfo = append(sitemapInfo, router.SitemapInfo{ Loc: "/" + page.Lang + "/blog/" + page.FileName, LastModified: page.ModifiedTime, Priority: 1.0, }) } return sitemapInfo } func (r *BlogPageHandler) AddMeta(c *fiber.Ctx, supplements *router.Supplements, lang string, templateMap fiber.Map) (meta []router.MetaField, err error) { metadata, _, err := supplements.BlogClient.ReadFrontmatter(lang + "/" + c.Params("title") + ".md") if err != nil { return nil, fmt.Errorf("failed to read frontmatter of the desired blog post: %w", err) } return []router.MetaField{ {Property: "og:title", Content: metadata.Title}, {Property: "og:description", Content: fmt.Sprintf("%s [%s]", metadata.ShortDescription, metadata.ActionDate)}, {Property: "og:image", Content: fmt.Sprintf(supplements.PhotoStorage.Thumbnail320p.BaseUrl, metadata.Thumbnail)}, {Property: "og:url", Content: fmt.Sprintf("%s/%s/blog/%s", templateMap["CanonicalEndpoint"], lang, c.Params("title"))}, {Property: "og:type", Content: "website"}, {Name: "twitter:card", Content: "summary_large_image"}, }, nil } func (r *BlogPageHandler) AddLinkedData(c *fiber.Ctx, supplements *router.Supplements, lang string, templateMap fiber.Map) (ld map[string]any, err error) { metadata, _, err := supplements.BlogClient.ReadFrontmatter(lang + "/" + c.Params("title") + ".md") if err != nil { return nil, fmt.Errorf("failed to read frontmatter of the desired blog post: %w", err) } return map[string]any{ "@context": "https://schema.org", "@type": "Article", "headline": metadata.Title, "description": fmt.Sprintf("%s [%s]", metadata.ShortDescription, metadata.ActionDate), "author": map[string]string{"@type": "Person", "name": "Saya Andy"}, "datePublished": metadata.PublishedTime.UTC().Format(time.RFC3339), }, nil } func (r *BlogPageHandler) RenderBody(c *fiber.Ctx, supplements *router.Supplements, lang string, templateMap fiber.Map) (statusCode int, err error) { _, pathParts, _, err := router.GetPathFromReferer(c) if err != nil { return fiber.StatusBadRequest, fmt.Errorf("failed to get path from referer: %w", err) } title := pathParts[2] metadata, parsedMarkdown, err := readBlogPost(supplements.MarkdownRenderer, supplements.BlogClient, lang+"/"+title) if err != nil { return fiber.StatusNotFound, fmt.Errorf("failed to find '%s' post: %w", title, err) } geolocationParts := strings.Split(metadata.Geolocation, " ") var x, y, areaError string if len(geolocationParts) >= 2 { x = geolocationParts[0] y = geolocationParts[1] } if len(geolocationParts) >= 3 { areaError = geolocationParts[2] } templateMap["MapLocationX"] = x templateMap["MapLocationY"] = y templateMap["MapLocationAreaMeters"] = areaError templateMap["Title"] = metadata.Title templateMap["Codename"] = title templateMap["ParsedMarkdown"] = template.HTML(parsedMarkdown) templateMap["PublishedDate"] = metadata.PublishedTime.Format("2006-01-02 15:04:05 -07:00") templateMap["ActionDate"] = metadata.ActionDate templateMap["ShortDescription"] = metadata.ShortDescription templateMap["Thumbnail"] = metadata.Thumbnail templateMap["Medley"] = metadata.Medley go supplements.ClientCache.View(c.IP(), title) return fiber.StatusOK, nil } func (r *BlogPageHandler) RenderHeader(c *fiber.Ctx, supplements *router.Supplements, lang string, templateMap fiber.Map) (statusCode int, err error) { path, pathParts, _, err := router.GetPathFromReferer(c) if err != nil { return fiber.StatusBadRequest, fmt.Errorf("failed to get path from referer: %w", err) } metadata, _, err := supplements.BlogClient.ReadFrontmatter(lang + "/" + pathParts[2] + ".md") if err != nil { return fiber.StatusNotFound, fmt.Errorf("could not read '%s' for metadata: %w", path, err) } templateMap["Title"] = metadata.Title return fiber.StatusOK, nil } func readBlogPost(md goldmark.Markdown, blogClient blog.Client, sourceName string) (metadata *frontmatter.Metadata, html string, err error) { metadata, markdown, err := blogClient.ReadFrontmatter(sourceName + ".md") if err != nil { return nil, "", fmt.Errorf("failed to read a frontmatter file: %w", err) } var buf bytes.Buffer if err := md.Convert(markdown, &buf); err != nil { return nil, "", fmt.Errorf("convert source context from md to html: %w", err) } return metadata, buf.String(), nil }