summaryrefslogtreecommitdiff
path: root/internal
diff options
from:
to:
context:
space:
mode:
Diffstat (limited to 'internal')
-rw-r--r--internal/b2/client.go17
-rw-r--r--internal/router/basic-handler.go18
-rw-r--r--internal/router/handlers/lang-blog-title.go49
-rw-r--r--internal/router/handlers/lang-blog.go17
-rw-r--r--internal/router/handlers/lang-map.go8
-rw-r--r--internal/router/handlers/lang-user.go11
-rw-r--r--internal/router/handlers/lang.go20
-rw-r--r--internal/router/handlers/robots.txt.go48
-rw-r--r--internal/router/handlers/root.go15
-rw-r--r--internal/router/handlers/sitemap.xml.go79
-rw-r--r--internal/router/router.go95
-rw-r--r--internal/templatemanager/templatemanager.go50
12 files changed, 368 insertions, 59 deletions
diff --git a/internal/b2/client.go b/internal/b2/client.go
index d712736..fda79e0 100644
--- a/internal/b2/client.go
+++ b/internal/b2/client.go
@@ -33,10 +33,11 @@ func NewB2Client(cfg *config.B2Config) (*B2Client, error) {
}
type BlogPage struct {
- Link string
- FileName string
- Lang string
- Metadata *frontmatter.Metadata
+ Link string
+ FileName string
+ Lang string
+ ModifiedTime time.Time
+ Metadata *frontmatter.Metadata
}
func (c *B2Client) Scan(prefix string) ([]*BlogPage, error) {
@@ -79,9 +80,13 @@ func (c *B2Client) Scan(prefix string) ([]*BlogPage, error) {
tags := strings.Split(attrs.Info["tags"], ",")
slices.Sort(tags)
+ lang, _ := strings.CutPrefix(linkParts[0], c.prefix)
+
filePaths = append(filePaths, &BlogPage{
- Link: obj.Name(),
- FileName: fileName,
+ Link: obj.Name(),
+ FileName: fileName,
+ Lang: lang,
+ ModifiedTime: attrs.LastModified,
Metadata: &frontmatter.Metadata{
Title: attrs.Info["title"],
ShortDescription: attrs.Info["short-description"],
diff --git a/internal/router/basic-handler.go b/internal/router/basic-handler.go
index 4a94a48..b973d01 100644
--- a/internal/router/basic-handler.go
+++ b/internal/router/basic-handler.go
@@ -34,6 +34,14 @@ func (r *BasicHandler) TemplatesToInject() []string {
return []string{}
}
+func (r *BasicHandler) SitemapInfo(supplements *Supplements) []SitemapInfo {
+ return nil
+}
+
+func (r *BasicHandler) ContentType() string {
+ return fiber.MIMETextHTMLCharsetUTF8
+}
+
func (r *BasicHandler) Render(c *fiber.Ctx, supplements *Supplements, lang string, templateMap fiber.Map) (statusCode int, err error) {
if !r.IsTemplated() {
panic("handler did not implement Render method (while being non-templated)")
@@ -41,10 +49,12 @@ func (r *BasicHandler) Render(c *fiber.Ctx, supplements *Supplements, lang strin
return fiber.StatusNoContent, nil
}
-func (r *BasicHandler) AddMeta(c *fiber.Ctx, supplements *Supplements, lang string, templateMap fiber.Map) (meta map[string]string, err error) {
- return map[string]string{
- "robots": "noindex,nofollow",
- }, nil
+func (r *BasicHandler) AddMeta(c *fiber.Ctx, supplements *Supplements, lang string, templateMap fiber.Map) (meta []MetaField, err error) {
+ return []MetaField{{Name: "robots", Content: "noindex,nofollow"}}, nil
+}
+
+func (r *BasicHandler) AddLinkedData(c *fiber.Ctx, supplements *Supplements, lang string, templateMap fiber.Map) (ld map[string]any, err error) {
+ return nil, nil
}
func (r *BasicHandler) RenderHeader(c *fiber.Ctx, supplements *Supplements, lang string, templateMap fiber.Map) (statusCode int, err error) {
diff --git a/internal/router/handlers/lang-blog-title.go b/internal/router/handlers/lang-blog-title.go
index 97de089..5bfc6e7 100644
--- a/internal/router/handlers/lang-blog-title.go
+++ b/internal/router/handlers/lang-blog-title.go
@@ -46,17 +46,54 @@ func (r *BlogPageHandler) ToValidateLang() router.LangSetting {
return router.InPath
}
-func (r *BlogPageHandler) AddMeta(c *fiber.Ctx, supplements *router.Supplements, lang string, templateMap fiber.Map) (meta map[string]string, err error) {
+func (r *BlogPageHandler) SitemapInfo(supplements *router.Supplements) []router.SitemapInfo {
+ sitemapInfo := []router.SitemapInfo{}
+
+ pages, err := supplements.B2Client.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.B2Client.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("https://f003.backblazeb2.com/file/sayana-photos/webp-320p/%s.webp", 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.B2Client.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]string{
- "og:title": metadata.Title,
- "og:description": fmt.Sprintf("%s [%s]", metadata.ShortDescription, metadata.ActionDate),
- "og:image": fmt.Sprintf("https://f003.backblazeb2.com/file/sayana-photos/webp-320p/%s.webp", metadata.Thumbnail),
- "twitter:card": "summary_large_image",
+ 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
}
diff --git a/internal/router/handlers/lang-blog.go b/internal/router/handlers/lang-blog.go
index e4c20c6..709d3dc 100644
--- a/internal/router/handlers/lang-blog.go
+++ b/internal/router/handlers/lang-blog.go
@@ -47,6 +47,23 @@ func (r *CatalogueHandler) ToValidateLang() router.LangSetting {
return router.InPath
}
+func (r *CatalogueHandler) SitemapInfo(supplements *router.Supplements) []router.SitemapInfo {
+ sitemapInfo := []router.SitemapInfo{}
+ for _, lang := range supplements.AvailableLanguages {
+ sitemapInfo = append(sitemapInfo, router.SitemapInfo{Loc: "/" + lang.Name + "/blog", Priority: 0.3})
+ }
+ return sitemapInfo
+}
+
+func (r *CatalogueHandler) AddMeta(c *fiber.Ctx, supplements *router.Supplements, lang string, templateMap fiber.Map) (meta []router.MetaField, err error) {
+ return []router.MetaField{
+ {Property: "og:title", Content: supplements.Localization[lang].BlogSearch.Header},
+ {Property: "og:description", Content: supplements.Localization[lang].BlogSearch.Description},
+ {Property: "og:url", Content: fmt.Sprintf("%s/%s/blog", templateMap["CanonicalEndpoint"], lang)},
+ {Property: "og:type", Content: "website"},
+ }, nil
+}
+
func (r *CatalogueHandler) RenderBody(c *fiber.Ctx, supplements *router.Supplements, lang string, templateMap fiber.Map) (statusCode int, err error) {
querySort := c.Query("sort")
if querySort == "" {
diff --git a/internal/router/handlers/lang-map.go b/internal/router/handlers/lang-map.go
index 7df6104..551b485 100644
--- a/internal/router/handlers/lang-map.go
+++ b/internal/router/handlers/lang-map.go
@@ -40,6 +40,14 @@ func (r *MapHandler) ToValidateLang() router.LangSetting {
return router.InPath
}
+func (r *MapHandler) SitemapInfo(supplements *router.Supplements) []router.SitemapInfo {
+ sitemapInfo := []router.SitemapInfo{}
+ for _, lang := range supplements.AvailableLanguages {
+ sitemapInfo = append(sitemapInfo, router.SitemapInfo{Loc: "/" + lang.Name + "/map", Priority: 0.3})
+ }
+ return sitemapInfo
+}
+
func (r *MapHandler) Render(c *fiber.Ctx, supplements *router.Supplements, lang string, templateMap fiber.Map) (statusCode int, err error) {
pages, err := supplements.B2Client.Scan(lang + "/")
slices.SortFunc(pages, func(a *b2.BlogPage, b *b2.BlogPage) int {
diff --git a/internal/router/handlers/lang-user.go b/internal/router/handlers/lang-user.go
index c22bcac..eae1685 100644
--- a/internal/router/handlers/lang-user.go
+++ b/internal/router/handlers/lang-user.go
@@ -37,10 +37,13 @@ func (r *UserHandler) ToValidateLang() router.LangSetting {
return router.InPath
}
-func (r *UserHandler) AddMeta(c *fiber.Ctx, supplements *router.Supplements, lang string, templateMap fiber.Map) (meta map[string]string, err error) {
- return map[string]string{
- "og:title": supplements.Localization[lang].BlogSearch.Header,
- "og:description": supplements.Localization[lang].BlogSearch.Description,
+func (r *UserHandler) AddMeta(c *fiber.Ctx, supplements *router.Supplements, lang string, templateMap fiber.Map) (meta []router.MetaField, err error) {
+ return []router.MetaField{
+ {Name: "robots", Content: "noindex,nofollow"},
+ {Property: "og:title", Content: supplements.Localization[lang].UserProfile.Header},
+ {Property: "og:description", Content: supplements.Localization[lang].UserProfile.Description},
+ {Property: "og:url", Content: fmt.Sprintf("%s/%s/user", templateMap["CanonicalEndpoint"], lang)},
+ {Property: "og:type", Content: "website"},
}, nil
}
diff --git a/internal/router/handlers/lang.go b/internal/router/handlers/lang.go
index 74166ae..c618353 100644
--- a/internal/router/handlers/lang.go
+++ b/internal/router/handlers/lang.go
@@ -41,11 +41,21 @@ func (r *HomeHandler) ToValidateLang() router.LangSetting {
return router.InPath
}
-func (r *HomeHandler) AddMeta(c *fiber.Ctx, supplements *router.Supplements, lang string, templateMap fiber.Map) (meta map[string]string, err error) {
- return map[string]string{
- "og:title": supplements.Localization[lang].HomePage.Header,
- "og:description": supplements.Localization[lang].HomePage.HomePageDescription,
- "og:image": "https://f003.backblazeb2.com/file/sayana-static/home-page-gifs/" + fmt.Sprintf("otter-%d.gif", rand.Int()%3+1),
+func (r *HomeHandler) SitemapInfo(supplements *router.Supplements) []router.SitemapInfo {
+ sitemapInfo := []router.SitemapInfo{}
+ for _, lang := range supplements.AvailableLanguages {
+ sitemapInfo = append(sitemapInfo, router.SitemapInfo{Loc: "/" + lang.Name, Priority: 0.7})
+ }
+ return sitemapInfo
+}
+
+func (r *HomeHandler) AddMeta(c *fiber.Ctx, supplements *router.Supplements, lang string, templateMap fiber.Map) (meta []router.MetaField, err error) {
+ return []router.MetaField{
+ {Property: "og:title", Content: supplements.Localization[lang].HomePage.Header},
+ {Property: "og:description", Content: supplements.Localization[lang].HomePage.HomePageDescription},
+ {Property: "og:image", Content: fmt.Sprintf("https://f003.backblazeb2.com/file/sayana-static/home-page-gifs/otter-%d.gif", rand.Int()%3+1)},
+ {Property: "og:url", Content: fmt.Sprintf("%s/%s", templateMap["CanonicalEndpoint"], lang)},
+ {Property: "og:type", Content: "website"},
}, nil
}
diff --git a/internal/router/handlers/robots.txt.go b/internal/router/handlers/robots.txt.go
new file mode 100644
index 0000000..fc60af4
--- /dev/null
+++ b/internal/router/handlers/robots.txt.go
@@ -0,0 +1,48 @@
+package handlers
+
+import (
+ "time"
+
+ "github.com/SayaAndy/saya-today-web/internal/router"
+ "github.com/gofiber/fiber/v2"
+)
+
+type RobotsTxtHandler struct {
+ router.BasicHandler
+}
+
+func init() {
+ router.Routes = append(router.Routes, &RobotsTxtHandler{})
+}
+
+func (r *RobotsTxtHandler) Filter() (method string, path string) {
+ return "GET", "/robots.txt"
+}
+
+func (r *RobotsTxtHandler) IsTemplated() bool {
+ return false
+}
+
+func (r *RobotsTxtHandler) TemplatesToInject() []string {
+ return []string{"views/pages/robots.txt"}
+}
+
+func (r *RobotsTxtHandler) ToCache() router.CacheSetting {
+ return router.ByUrlOnly
+}
+
+func (r *RobotsTxtHandler) CacheDuration() time.Duration {
+ return time.Hour
+}
+
+func (r *RobotsTxtHandler) ToValidateLang() router.LangSetting {
+ return router.NotRequired
+}
+
+func (r *RobotsTxtHandler) ContentType() string {
+ return fiber.MIMETextPlainCharsetUTF8
+}
+
+func (r *RobotsTxtHandler) Render(c *fiber.Ctx, supplements *router.Supplements, lang string, templateMap fiber.Map) (statusCode int, err error) {
+ return fiber.StatusOK, nil
+}
diff --git a/internal/router/handlers/root.go b/internal/router/handlers/root.go
index 9f15782..cd01fcd 100644
--- a/internal/router/handlers/root.go
+++ b/internal/router/handlers/root.go
@@ -1,6 +1,7 @@
package handlers
import (
+ "fmt"
"time"
"github.com/SayaAndy/saya-today-web/internal/router"
@@ -35,10 +36,16 @@ func (r *RootHandler) CacheDuration() time.Duration {
return 24 * time.Hour
}
-func (r *RootHandler) AddMeta(c *fiber.Ctx, supplements *router.Supplements, lang string, templateMap fiber.Map) (meta map[string]string, err error) {
- return map[string]string{
- "og:title": "Saya Blog",
- "og:description": "The blog of a 25 years old, travelling God knows where, proud to be not mainstream // Личный блог 25-летки, странствующего по ебеням, зато не мейнстрим",
+func (r *RootHandler) SitemapInfo(_ *router.Supplements) []router.SitemapInfo {
+ return []router.SitemapInfo{{Loc: "/", Priority: 0.7}}
+}
+
+func (r *RootHandler) AddMeta(c *fiber.Ctx, supplements *router.Supplements, lang string, templateMap fiber.Map) (meta []router.MetaField, err error) {
+ return []router.MetaField{
+ {Property: "og:title", Content: "Saya Blog"},
+ {Property: "og:description", Content: "The blog of a 25 years old, travelling God knows where, proud to be not mainstream // Личный блог 25-летки, странствующего по ебеням, зато не мейнстрим"},
+ {Property: "og:url", Content: fmt.Sprint(templateMap["CanonicalEndpoint"]) + "/"},
+ {Property: "og:type", Content: "website"},
}, nil
}
diff --git a/internal/router/handlers/sitemap.xml.go b/internal/router/handlers/sitemap.xml.go
new file mode 100644
index 0000000..bb1df93
--- /dev/null
+++ b/internal/router/handlers/sitemap.xml.go
@@ -0,0 +1,79 @@
+package handlers
+
+import (
+ "log/slog"
+ "time"
+
+ "github.com/SayaAndy/saya-today-web/internal/router"
+ "github.com/gofiber/fiber/v2"
+)
+
+type SitemapXmlHandler struct {
+ router.BasicHandler
+}
+
+func init() {
+ router.Routes = append(router.Routes, &SitemapXmlHandler{})
+}
+
+func (r *SitemapXmlHandler) Filter() (method string, path string) {
+ return "GET", "/sitemap.xml"
+}
+
+func (r *SitemapXmlHandler) IsTemplated() bool {
+ return false
+}
+
+func (r *SitemapXmlHandler) TemplatesToInject() []string {
+ return []string{"views/pages/sitemap.xml"}
+}
+
+func (r *SitemapXmlHandler) ToCache() router.CacheSetting {
+ return router.ByUrlOnly
+}
+
+func (r *SitemapXmlHandler) CacheDuration() time.Duration {
+ return time.Hour
+}
+
+func (r *SitemapXmlHandler) ToValidateLang() router.LangSetting {
+ return router.NotRequired
+}
+
+func (r *SitemapXmlHandler) ContentType() string {
+ return fiber.MIMEApplicationXMLCharsetUTF8
+}
+
+func (r *SitemapXmlHandler) Render(c *fiber.Ctx, supplements *router.Supplements, lang string, templateMap fiber.Map) (statusCode int, err error) {
+ urls := []router.SitemapInfo{}
+ for _, route := range router.Routes {
+ sitemapInfo := route.SitemapInfo(supplements)
+ if sitemapInfo == nil {
+ continue
+ }
+
+ method, match := route.Filter()
+ lastMod, err := supplements.TemplateManager.GetLastModified(method + " " + match)
+ if err != nil {
+ slog.Warn("failed to get last modified for a route",
+ slog.String("error", err.Error()),
+ slog.String("method", method),
+ slog.String("match", match))
+ lastMod = time.Now()
+ }
+
+ for i := range sitemapInfo {
+ if sitemapInfo[i].LastModified.Before(lastMod) {
+ sitemapInfo[i].LastModified = lastMod
+ }
+ if sitemapInfo[i].ChangeFreq == "" {
+ sitemapInfo[i].ChangeFreq = "monthly"
+ }
+ urls = append(urls, sitemapInfo[i])
+ }
+ }
+
+ templateMap["URLs"] = urls
+
+ return fiber.StatusOK, nil
+}
diff --git a/internal/router/router.go b/internal/router/router.go
index f28add2..0d0ce8c 100644
--- a/internal/router/router.go
+++ b/internal/router/router.go
@@ -2,8 +2,10 @@ package router
import (
"database/sql"
+ "encoding/json"
"errors"
"fmt"
+ "html/template"
"log/slog"
"net/url"
"slices"
@@ -21,7 +23,9 @@ import (
"github.com/SayaAndy/saya-today-web/locale"
"github.com/dgraph-io/ristretto/v2"
"github.com/gofiber/fiber/v2"
+ "github.com/gofiber/fiber/v2/middleware/compress"
"github.com/gofiber/fiber/v2/middleware/cors"
+ "github.com/gofiber/fiber/v2/middleware/etag"
"github.com/golang-migrate/migrate/v4"
"github.com/golang-migrate/migrate/v4/database/sqlite3"
"github.com/yuin/goldmark"
@@ -52,6 +56,19 @@ var (
Routes = make([]Route, 0)
)
+type SitemapInfo struct {
+ Loc string
+ LastModified time.Time
+ ChangeFreq string
+ Priority float32
+}
+
+type MetaField struct {
+ Name string
+ Property string
+ Content string
+}
+
type Route interface {
Filter() (method string, path string)
IsTemplated() bool
@@ -59,8 +76,11 @@ type Route interface {
CacheDuration() time.Duration
ToValidateLang() LangSetting
TemplatesToInject() []string
+ SitemapInfo(supplements *Supplements) []SitemapInfo
+ ContentType() string
Render(c *fiber.Ctx, supplements *Supplements, lang string, templateMap fiber.Map) (statusCode int, err error)
- AddMeta(c *fiber.Ctx, supplements *Supplements, lang string, templateMap fiber.Map) (meta map[string]string, err error)
+ AddMeta(c *fiber.Ctx, supplements *Supplements, lang string, templateMap fiber.Map) (meta []MetaField, err error)
+ AddLinkedData(c *fiber.Ctx, supplements *Supplements, lang string, templateMap fiber.Map) (ld map[string]any, err error)
RenderHeader(c *fiber.Ctx, supplements *Supplements, lang string, templateMap fiber.Map) (statusCode int, err error)
RenderBody(c *fiber.Ctx, supplements *Supplements, lang string, templateMap fiber.Map) (statusCode int, err error)
RenderFooter(c *fiber.Ctx, supplements *Supplements, lang string, templateMap fiber.Map) (statusCode int, err error)
@@ -87,6 +107,7 @@ type Router struct {
app *fiber.App
templatedRoutes map[string]map[string]Route
templatedPathMatcher *PathMatcher
+ canonicalEndpoint string
}
func NewRouter(cfg *config.Config) (*Router, error) {
@@ -210,10 +231,24 @@ func NewRouter(cfg *config.Config) (*Router, error) {
AllowHeaders: "Origin, Content-Type, Accept",
}))
+ app.Use(compress.New(compress.Config{
+ Level: compress.LevelBestSpeed,
+ }))
+
+ app.Use(etag.New())
+
+ app.Use(func(c *fiber.Ctx) error {
+ path := c.Path()
+ if len(path) > 1 && path[len(path)-1] == '/' {
+ return c.Redirect(strings.TrimRight(path, "/"), fiber.StatusMovedPermanently)
+ }
+ return c.Next()
+ })
+
templatedRoutes := make(map[string]map[string]Route)
templatedPathMatcher := NewPathMatcher()
- return &Router{supplements, app, templatedRoutes, templatedPathMatcher}, nil
+ return &Router{supplements, app, templatedRoutes, templatedPathMatcher, cfg.CanonicalEndpoint}, nil
}
func (r *Router) InitRoutes() (err error) {
@@ -261,10 +296,11 @@ func (r *Router) InitRoutes() (err error) {
}
defaultMap := fiber.Map{
- "L": r.supplements.Localization[lang],
- "Lang": lang,
- "Path": trimmedPath,
- "QueryString": queryString,
+ "L": r.supplements.Localization[lang],
+ "Lang": lang,
+ "Path": trimmedPath,
+ "QueryString": queryString,
+ "CanonicalEndpoint": r.canonicalEndpoint,
}
statusCode, err := currentRoute.Render(c, r.supplements, lang, defaultMap)
@@ -303,8 +339,8 @@ func (r *Router) InitRoutes() (err error) {
go r.supplements.PageCache.SetWithTTL(cacheKey, content, int64(len(content)), route.CacheDuration())
}
- c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8)
- return c.Status(statusCode).Type("html").Send(content)
+ c.Set(fiber.HeaderContentType, route.ContentType())
+ return c.Status(statusCode).Send(content)
})
}
}
@@ -327,7 +363,7 @@ func (r *Router) InitRoutes() (err error) {
r.app.Static("/", "./static")
- Routes = make([]Route, 0)
+ // Routes = make([]Route, 0)
return nil
}
@@ -379,14 +415,15 @@ func (r *Router) generalPage(c *fiber.Ctx, route Route, lang string) error {
}
if val, ok := r.supplements.PageCache.Get(cacheKey); val != nil && ok {
- c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8)
- return c.Status(fiber.StatusOK).Type("html").Send(val)
+ c.Set(fiber.HeaderContentType, route.ContentType())
+ return c.Status(fiber.StatusOK).Send(val)
}
valueMap := fiber.Map{
- "L": r.supplements.Localization[lang],
- "Lang": lang,
- "QueryString": queryString,
+ "L": r.supplements.Localization[lang],
+ "Lang": lang,
+ "QueryString": queryString,
+ "CanonicalEndpoint": r.canonicalEndpoint,
}
var err error
@@ -394,6 +431,15 @@ func (r *Router) generalPage(c *fiber.Ctx, route Route, lang string) error {
slog.Warn("failed to receive meta", slog.String("path", path), slog.String("error", err.Error()))
}
+ var ld map[string]any
+ if ld, err = route.AddLinkedData(c, r.supplements, lang, valueMap); err != nil {
+ slog.Warn("failed to receive linked data", slog.String("path", path), slog.String("error", err.Error()))
+ }
+ if ld != nil {
+ ldBytes, _ := json.Marshal(ld)
+ valueMap["LinkedData"] = template.JS(ldBytes)
+ }
+
content, err := r.supplements.TemplateManager.Render("general-page", valueMap)
if err != nil {
slog.Warn("failed to generate div", slog.String("path", path), slog.String("error", err.Error()))
@@ -401,8 +447,8 @@ func (r *Router) generalPage(c *fiber.Ctx, route Route, lang string) error {
}
go r.supplements.PageCache.SetWithTTL(cacheKey, content, int64(len(content)), route.CacheDuration())
- c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8)
- return c.Status(fiber.StatusOK).Type("html").Send(content)
+ c.Set(fiber.HeaderContentType, route.ContentType())
+ return c.Status(fiber.StatusOK).Send(content)
}
func (r *Router) generalPageSegment(c *fiber.Ctx, part string) error {
@@ -447,17 +493,18 @@ func (r *Router) generalPageSegment(c *fiber.Ctx, part string) error {
if route.ToCache() != Disabled {
if val, ok := r.supplements.PageCache.Get(cacheKey); val != nil && ok {
- c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8)
- return c.Status(fiber.StatusOK).Type("html").Send(val)
+ c.Set(fiber.HeaderContentType, route.ContentType())
+ return c.Status(fiber.StatusOK).Send(val)
}
}
var statusCode int
defaultMap := fiber.Map{
- "L": r.supplements.Localization[lang],
- "Lang": lang,
- "Path": strings.Trim(path, "/"),
- "QueryString": queryString,
+ "L": r.supplements.Localization[lang],
+ "Lang": lang,
+ "Path": strings.Trim(path, "/"),
+ "QueryString": queryString,
+ "CanonicalEndpoint": r.canonicalEndpoint,
}
switch part {
@@ -505,8 +552,8 @@ func (r *Router) generalPageSegment(c *fiber.Ctx, part string) error {
go r.supplements.PageCache.SetWithTTL(cacheKey, content, int64(len(content)), route.CacheDuration())
}
- c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8)
- return c.Status(statusCode).Type("html").Send(content)
+ c.Set(fiber.HeaderContentType, route.ContentType())
+ return c.Status(statusCode).Send(content)
}
func (r *Router) getAndValidateLang(c *fiber.Ctx, langSetting LangSetting, defaultLang ...string) (string, error) {
diff --git a/internal/templatemanager/templatemanager.go b/internal/templatemanager/templatemanager.go
index b23e2e8..9d14e15 100644
--- a/internal/templatemanager/templatemanager.go
+++ b/internal/templatemanager/templatemanager.go
@@ -4,8 +4,10 @@ import (
"bytes"
"fmt"
"html/template"
+ "os"
"path/filepath"
"strings"
+ "time"
)
type TemplateManager struct {
@@ -13,8 +15,9 @@ type TemplateManager struct {
}
type templateManagerRender struct {
- Main string
- Tmpl *template.Template
+ Main string
+ Tmpl *template.Template
+ LastModified time.Time
}
type TemplateManagerTemplates struct {
@@ -53,10 +56,15 @@ func NewTemplateManager(templates ...TemplateManagerTemplates) (*TemplateManager
if err != nil {
return nil, err
}
+ modTime, err := setLastModified(tmplStruct.Files...)
+ if err != nil {
+ return nil, err
+ }
templateMap[tmplStruct.Name] = templateManagerRender{
- Main: filepath.Base(tmplStruct.Files[0]),
- Tmpl: tmpl,
+ Main: filepath.Base(tmplStruct.Files[0]),
+ Tmpl: tmpl,
+ LastModified: modTime,
}
}
@@ -114,10 +122,40 @@ func (tm *TemplateManager) Add(name string, files ...string) error {
return fmt.Errorf("failed to add template into manager: %w", err)
}
+ modTime, err := setLastModified(files...)
+ if err != nil {
+ return fmt.Errorf("failed to add template into manager: %w", err)
+ }
+
tm.templates[name] = templateManagerRender{
- Main: filepath.Base(files[0]),
- Tmpl: tmpl,
+ Main: filepath.Base(files[0]),
+ Tmpl: tmpl,
+ LastModified: modTime,
}
return nil
}
+
+func (tm *TemplateManager) GetLastModified(name string) (time.Time, error) {
+ tmpl, exists := tm.templates[name]
+ if !exists {
+ return time.Time{}, fmt.Errorf("template %s not found", name)
+ }
+
+ return tmpl.LastModified, nil
+}
+
+func setLastModified(filenames ...string) (time.Time, error) {
+ lastModified := time.Time{}
+ for _, filename := range filenames {
+ stat, err := os.Stat(filename)
+ if err != nil {
+ return lastModified, fmt.Errorf("failed to stat file: path '%s': %w", filename, err)
+ }
+ modTime := stat.ModTime()
+ if lastModified.Before(modTime) {
+ lastModified = modTime
+ }
+ }
+ return lastModified, nil
+}