From 5eb7b64b3da54e769cc6e52fd6918e4b465b8c0a Mon Sep 17 00:00:00 2001 From: SayaAndy Date: Sun, 31 Aug 2025 13:16:45 +0700 Subject: feat: integrate partials in page generating --- main.go | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'main.go') diff --git a/main.go b/main.go index 020f8ef..ace9916 100644 --- a/main.go +++ b/main.go @@ -124,10 +124,14 @@ func main() { app.Get("/:lang/map", router.Lang_Map(localization, availableLanguages, b2Client)) app.Get("/:lang/blog", router.Lang_Blog(localization, availableLanguages, b2Client)) app.Get("/:lang/blog/:title", router.Lang_Blog_Title(localization, availableLanguages, b2Client, md)) + app.Get("/api/v1/tz", router.Api_V1_TZ()) app.Get("/api/v1/blog-search", router.Api_V1_BlogSearch(localization, availableLanguages, b2Client)) app.Get("/api/v1/like", router.Api_V1_Like_Get(localization, b2Client)) app.Put("/api/v1/like", router.Api_V1_Like_Put(localization, b2Client)) + app.Get("/api/v1/general-page/header", router.Api_V1_GeneralPage_Header(localization, availableLanguages, b2Client)) + app.Get("/api/v1/general-page/body", router.Api_V1_GeneralPage_Body(localization, availableLanguages, b2Client, md)) + app.Get("/api/v1/general-page/footer", router.Api_V1_GeneralPage_Footer(localization, availableLanguages)) app.Static("/", "./static") -- cgit v1.3.1+13 From 145e7e05f3aa6adbc71a0db5e7da160e626bacd3 Mon Sep 17 00:00:00 2001 From: SayaAndy Date: Sun, 31 Aug 2025 18:12:51 +0700 Subject: feat: seamless single page application --- internal/lightgallery/html_renderer.go | 2 +- internal/router/api-v1-general-page-body.go | 8 +- .../router/api-v1-general-page-bottom-embeds.go | 65 +++++++++++++++ internal/router/api-v1-general-page-footer.go | 5 +- internal/router/api-v1-general-page-header.go | 5 +- internal/router/api-v1-general-page-top-embeds.go | 64 +++++++++++++++ internal/router/lang-blog-title.go | 1 + internal/router/lang-blog.go | 1 + main.go | 2 + views/layouts/general-page.html | 94 +++++++--------------- views/pages/blog-catalogue.html | 36 +++++---- views/pages/blog-page.html | 79 ++++++++++++++++++ views/partials/catalogue-blog-cards.html | 5 +- views/partials/general-page-bottom-embeds.html | 1 + views/partials/general-page-header.html | 2 +- views/partials/general-page-top-embeds.html | 1 + 16 files changed, 278 insertions(+), 93 deletions(-) create mode 100644 internal/router/api-v1-general-page-bottom-embeds.go create mode 100644 internal/router/api-v1-general-page-top-embeds.go create mode 100644 views/partials/general-page-bottom-embeds.html create mode 100644 views/partials/general-page-top-embeds.html (limited to 'main.go') diff --git a/internal/lightgallery/html_renderer.go b/internal/lightgallery/html_renderer.go index 21bd049..c8c1cf0 100644 --- a/internal/lightgallery/html_renderer.go +++ b/internal/lightgallery/html_renderer.go @@ -126,7 +126,7 @@ document.addEventListener('htmx:afterRequest', (e) => { if (e.detail.target.id == 'general-page-body') { createLightGallery%s(); } -}); +}, {once: true}); `, galleryID, galleryID, strings.Join(dynamicElements, ","), galleryID, galleryID)) } diff --git a/internal/router/api-v1-general-page-body.go b/internal/router/api-v1-general-page-body.go index 419bad6..4108a4f 100644 --- a/internal/router/api-v1-general-page-body.go +++ b/internal/router/api-v1-general-page-body.go @@ -44,8 +44,9 @@ func Api_V1_GeneralPage_Body(l map[string]*locale.LocaleConfig, langs []string, } values := fiber.Map{ - "L": l[lang], - "Lang": lang, + "L": l[lang], + "Lang": lang, + "QueryString": string(c.Request().URI().QueryString()), } var additionalTemplates []string @@ -117,10 +118,11 @@ func Api_V1_GeneralPage_Body(l map[string]*locale.LocaleConfig, langs []string, areaError = geolocationParts[2] } - values["ParsedMarkdown"] = template.HTML(parsedMarkdown) values["MapLocationX"] = x values["MapLocationY"] = y values["MapLocationAreaMeters"] = areaError + values["Title"] = metadata.Title + values["ParsedMarkdown"] = template.HTML(parsedMarkdown) additionalTemplates = append(additionalTemplates, "views/pages/blog-page.html") } diff --git a/internal/router/api-v1-general-page-bottom-embeds.go b/internal/router/api-v1-general-page-bottom-embeds.go new file mode 100644 index 0000000..f0957f3 --- /dev/null +++ b/internal/router/api-v1-general-page-bottom-embeds.go @@ -0,0 +1,65 @@ +package router + +import ( + "fmt" + "log/slog" + "net/url" + "slices" + "strings" + + "github.com/SayaAndy/saya-today-web/internal/b2" + "github.com/SayaAndy/saya-today-web/locale" + "github.com/gofiber/fiber/v2" +) + +func init() { + tm.Add("general-page-bottom-embeds", "views/partials/general-page-bottom-embeds.html") +} + +func Api_V1_GeneralPage_BottomEmbeds(l map[string]*locale.LocaleConfig, langs []string, b2Client *b2.B2Client) func(c *fiber.Ctx) error { + return func(c *fiber.Ctx) error { + c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) + + referer := c.Get("Referer", "") + if referer == "" { + return c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is empty") + } + urlStruct, err := url.ParseRequestURI(referer) + if err != nil { + return c.Status(fiber.ErrBadRequest.Code).SendString(fmt.Sprintf("'Referer' header is invalid: %s", err.Error())) + } + + path := urlStruct.EscapedPath() + pathParts := strings.Split(strings.Trim(path, "/"), "/") + if len(pathParts) < 2 { + return c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is invalid: expect format '/{lang}/...'") + } + + lang := pathParts[0] + if !slices.Contains(langs, lang) { + return c.Status(fiber.ErrNotFound.Code).SendString(fmt.Sprintf("server does not support '%s' language... yet??", lang)) + } + + values := fiber.Map{ + "L": l[lang], + "Lang": lang, + "QueryString": string(c.Request().URI().QueryString()), + } + var additionalTemplates []string + + if len(pathParts) == 2 && pathParts[1] == "blog" { + additionalTemplates = append(additionalTemplates, "views/pages/blog-catalogue.html") + } else if len(pathParts) == 3 && pathParts[1] == "blog" { + additionalTemplates = append(additionalTemplates, "views/pages/blog-page.html") + } + + content, err := tm.Render("general-page-bottom-embeds", values, additionalTemplates...) + if err != nil { + slog.Warn("failed to generate div", slog.String("path", path), slog.String("error", err.Error())) + return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate div") + } + + c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8) + return c.Status(fiber.StatusOK).Type("html").Send(content) + } +} diff --git a/internal/router/api-v1-general-page-footer.go b/internal/router/api-v1-general-page-footer.go index 4c3a35e..39b6bea 100644 --- a/internal/router/api-v1-general-page-footer.go +++ b/internal/router/api-v1-general-page-footer.go @@ -40,8 +40,9 @@ func Api_V1_GeneralPage_Footer(l map[string]*locale.LocaleConfig, langs []string } values := fiber.Map{ - "L": l[lang], - "Lang": lang, + "L": l[lang], + "Lang": lang, + "QueryString": string(c.Request().URI().QueryString()), } var additionalTemplates []string diff --git a/internal/router/api-v1-general-page-header.go b/internal/router/api-v1-general-page-header.go index 045ff8b..0529ba6 100644 --- a/internal/router/api-v1-general-page-header.go +++ b/internal/router/api-v1-general-page-header.go @@ -41,8 +41,9 @@ func Api_V1_GeneralPage_Header(l map[string]*locale.LocaleConfig, langs []string } values := fiber.Map{ - "L": l[lang], - "Lang": lang, + "L": l[lang], + "Lang": lang, + "QueryString": string(c.Request().URI().QueryString()), } var additionalTemplates []string diff --git a/internal/router/api-v1-general-page-top-embeds.go b/internal/router/api-v1-general-page-top-embeds.go new file mode 100644 index 0000000..090107b --- /dev/null +++ b/internal/router/api-v1-general-page-top-embeds.go @@ -0,0 +1,64 @@ +package router + +import ( + "fmt" + "log/slog" + "net/url" + "slices" + "strings" + + "github.com/SayaAndy/saya-today-web/locale" + "github.com/gofiber/fiber/v2" +) + +func init() { + tm.Add("general-page-top-embeds", "views/partials/general-page-top-embeds.html") +} + +func Api_V1_GeneralPage_TopEmbeds(l map[string]*locale.LocaleConfig, langs []string) func(c *fiber.Ctx) error { + return func(c *fiber.Ctx) error { + c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) + + referer := c.Get("Referer", "") + if referer == "" { + return c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is empty") + } + urlStruct, err := url.ParseRequestURI(referer) + if err != nil { + return c.Status(fiber.ErrBadRequest.Code).SendString(fmt.Sprintf("'Referer' header is invalid: %s", err.Error())) + } + + path := urlStruct.EscapedPath() + pathParts := strings.Split(strings.Trim(path, "/"), "/") + if len(pathParts) < 2 { + return c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is invalid: expect format '/{lang}/...'") + } + + lang := pathParts[0] + if !slices.Contains(langs, lang) { + return c.Status(fiber.ErrNotFound.Code).SendString(fmt.Sprintf("server does not support '%s' language... yet??", lang)) + } + + values := fiber.Map{ + "L": l[lang], + "Lang": lang, + "QueryString": string(c.Request().URI().QueryString()), + } + var additionalTemplates []string + + if len(pathParts) == 2 && pathParts[1] == "blog" { + additionalTemplates = append(additionalTemplates, "views/pages/blog-catalogue.html") + } else if len(pathParts) == 3 && pathParts[1] == "blog" { + additionalTemplates = append(additionalTemplates, "views/pages/blog-page.html") + } + + content, err := tm.Render("general-page-top-embeds", values, additionalTemplates...) + if err != nil { + slog.Warn("failed to generate div", slog.String("path", path), slog.String("error", err.Error())) + return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate div") + } + + c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8) + return c.Status(fiber.StatusOK).Type("html").Send(content) + } +} diff --git a/internal/router/lang-blog-title.go b/internal/router/lang-blog-title.go index bafb0d2..d6ca917 100644 --- a/internal/router/lang-blog-title.go +++ b/internal/router/lang-blog-title.go @@ -56,6 +56,7 @@ func Lang_Blog_Title(l map[string]*locale.LocaleConfig, langs []string, b2Client "MapLocationY": y, "MapLocationAreaMeters": areaError, "Lang": lang, + "QueryString": string(c.Request().URI().QueryString()), }) if err != nil { slog.Warn("failed to generate page", slog.String("page", "/"+lang+"/blog/"+c.Params("title")), slog.String("error", err.Error())) diff --git a/internal/router/lang-blog.go b/internal/router/lang-blog.go index dd3f079..38a6597 100644 --- a/internal/router/lang-blog.go +++ b/internal/router/lang-blog.go @@ -76,6 +76,7 @@ func Lang_Blog(l map[string]*locale.LocaleConfig, langs []string, b2Client *b2.B content, err := tm.Render("blog-catalogue", fiber.Map{ "QuerySort": querySort, "QueryTags": strings.Join(queryTags, ","), + "QueryString": string(c.Request().URI().QueryString()), "Tags": tagsArray, "Lang": lang, "L": l[lang], diff --git a/main.go b/main.go index ace9916..72eca3d 100644 --- a/main.go +++ b/main.go @@ -132,6 +132,8 @@ func main() { app.Get("/api/v1/general-page/header", router.Api_V1_GeneralPage_Header(localization, availableLanguages, b2Client)) app.Get("/api/v1/general-page/body", router.Api_V1_GeneralPage_Body(localization, availableLanguages, b2Client, md)) app.Get("/api/v1/general-page/footer", router.Api_V1_GeneralPage_Footer(localization, availableLanguages)) + app.Get("/api/v1/general-page/top-embeds", router.Api_V1_GeneralPage_TopEmbeds(localization, availableLanguages)) + app.Get("/api/v1/general-page/bottom-embeds", router.Api_V1_GeneralPage_BottomEmbeds(localization, availableLanguages, b2Client)) app.Static("/", "./static") diff --git a/views/layouts/general-page.html b/views/layouts/general-page.html index 4e352f4..e9a6c0f 100644 --- a/views/layouts/general-page.html +++ b/views/layouts/general-page.html @@ -8,7 +8,6 @@ - {{ block "top-embeds" . }}{{ end }} @@ -18,6 +17,16 @@ + + - +
+
+
+
+
+
+ - - diff --git a/views/pages/blog-catalogue.html b/views/pages/blog-catalogue.html index ac2fb7d..a5f5256 100644 --- a/views/pages/blog-catalogue.html +++ b/views/pages/blog-catalogue.html @@ -98,6 +98,7 @@ function cleanHrefParams() { const url = new URL(window.location.href); url.search = ''; + window.history.pushState({}, '', url.toString()); } @@ -137,22 +138,27 @@ window.history.pushState({}, '', url.toString()); } - document.addEventListener('DOMContentLoaded', contextAll); - + document.addEventListener('htmx:afterRequest', (e) => { + if (e.detail.xhr.status == 404 || e.detail.successful != true) { + return console.error(e); + } + if (e.detail.target.id == 'general-page-body') { + contextAll(); - {{ end }} diff --git a/views/pages/blog-page.html b/views/pages/blog-page.html index d8fb118..e68fcab 100644 --- a/views/pages/blog-page.html +++ b/views/pages/blog-page.html @@ -1,3 +1,18 @@ +{{ define "top-embeds" }} + +{{ end }} + {{ define "body" }}
{{ .ParsedMarkdown }} @@ -5,6 +20,43 @@

+ + {{- end }}
{{ end }} @@ -12,3 +64,30 @@ {{ define "footer" }}
{{ end }} + +{{ define "bottom-embeds" }} + +{{ end }} \ No newline at end of file diff --git a/views/partials/catalogue-blog-cards.html b/views/partials/catalogue-blog-cards.html index 35c7ad7..06d99d5 100644 --- a/views/partials/catalogue-blog-cards.html +++ b/views/partials/catalogue-blog-cards.html @@ -1,10 +1,10 @@ {{- range .BlogPages }}
- +

{{ .ShortDescription }}

diff --git a/views/partials/general-page-bottom-embeds.html b/views/partials/general-page-bottom-embeds.html new file mode 100644 index 0000000..4c36b26 --- /dev/null +++ b/views/partials/general-page-bottom-embeds.html @@ -0,0 +1 @@ +{{ block "bottom-embeds" . }}{{ end }} \ No newline at end of file diff --git a/views/partials/general-page-header.html b/views/partials/general-page-header.html index 66cfecf..63803bd 100644 --- a/views/partials/general-page-header.html +++ b/views/partials/general-page-header.html @@ -1,6 +1,6 @@
- +

//

{{ .Title }}

{{ .PublishedDate }}

diff --git a/views/partials/general-page-top-embeds.html b/views/partials/general-page-top-embeds.html new file mode 100644 index 0000000..1407b50 --- /dev/null +++ b/views/partials/general-page-top-embeds.html @@ -0,0 +1 @@ +{{ block "top-embeds" . }}{{ end }} \ No newline at end of file -- cgit v1.3.1+13 From 6af14241ff2c8050753f1d6ac702a938a017a839 Mon Sep 17 00:00:00 2001 From: SayaAndy Date: Sun, 31 Aug 2025 18:40:13 +0700 Subject: feat: add page cache --- go.mod | 3 +++ go.sum | 6 ++++++ internal/router/api-v1-general-page-body.go | 9 +++++++++ internal/router/api-v1-general-page-bottom-embeds.go | 8 ++++++++ internal/router/api-v1-general-page-footer.go | 9 +++++++++ internal/router/api-v1-general-page-header.go | 9 +++++++++ internal/router/api-v1-general-page-top-embeds.go | 9 +++++++++ internal/router/client-cache.go | 5 ----- internal/router/page-cache.go | 5 +++++ main.go | 14 +++++++++++++- 10 files changed, 71 insertions(+), 6 deletions(-) create mode 100644 internal/router/page-cache.go (limited to 'main.go') diff --git a/go.mod b/go.mod index 71aa20b..e7547b1 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,9 @@ require ( require ( github.com/andybalholm/brotli v1.2.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/dgraph-io/ristretto/v2 v2.3.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect github.com/gabriel-vasile/mimetype v1.4.10 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect diff --git a/go.sum b/go.sum index 22ef8ff..bbfad9e 100644 --- a/go.sum +++ b/go.sum @@ -2,9 +2,15 @@ github.com/Backblaze/blazer v0.7.2 h1:UWNHMLB+Nf+UmbO2qkVvgriODLEMz4kIyr2Hm+DVXQ github.com/Backblaze/blazer v0.7.2/go.mod h1:T4y3EYa9IQ5J0PKc/C/J8/CEnSd3qa/lgNw938wZg10= github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgraph-io/ristretto/v2 v2.3.0 h1:qTQ38m7oIyd4GAed/QkUZyPFNMnvVWyazGXRwvOt5zk= +github.com/dgraph-io/ristretto/v2 v2.3.0/go.mod h1:gpoRV3VzrEY1a9dWAYV6T1U7YzfgttXdd/ZzL1s9OZM= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0= github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= diff --git a/internal/router/api-v1-general-page-body.go b/internal/router/api-v1-general-page-body.go index 4108a4f..2329c4a 100644 --- a/internal/router/api-v1-general-page-body.go +++ b/internal/router/api-v1-general-page-body.go @@ -8,6 +8,7 @@ import ( "regexp" "slices" "strings" + "time" "github.com/SayaAndy/saya-today-web/internal/b2" "github.com/SayaAndy/saya-today-web/locale" @@ -33,6 +34,13 @@ func Api_V1_GeneralPage_Body(l map[string]*locale.LocaleConfig, langs []string, } path := urlStruct.EscapedPath() + + cacheKey := fmt.Sprintf("body.%s", path) + if val, ok := PCache.Get(cacheKey); val != nil && ok { + c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8) + return c.Status(fiber.StatusOK).Type("html").Send(val) + } + pathParts := strings.Split(strings.Trim(path, "/"), "/") if len(pathParts) < 2 { return c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is invalid: expect format '/{lang}/...'") @@ -133,6 +141,7 @@ func Api_V1_GeneralPage_Body(l map[string]*locale.LocaleConfig, langs []string, return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate div") } + go PCache.SetWithTTL(cacheKey, content, int64(len(content)), 5*time.Minute) c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8) return c.Status(fiber.StatusOK).Type("html").Send(content) } diff --git a/internal/router/api-v1-general-page-bottom-embeds.go b/internal/router/api-v1-general-page-bottom-embeds.go index f0957f3..df766a7 100644 --- a/internal/router/api-v1-general-page-bottom-embeds.go +++ b/internal/router/api-v1-general-page-bottom-embeds.go @@ -6,6 +6,7 @@ import ( "net/url" "slices" "strings" + "time" "github.com/SayaAndy/saya-today-web/internal/b2" "github.com/SayaAndy/saya-today-web/locale" @@ -30,6 +31,12 @@ func Api_V1_GeneralPage_BottomEmbeds(l map[string]*locale.LocaleConfig, langs [] } path := urlStruct.EscapedPath() + cacheKey := fmt.Sprintf("bottom-embeds.%s", path) + if val, ok := PCache.Get(cacheKey); val != nil && ok { + c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8) + return c.Status(fiber.StatusOK).Type("html").Send(val) + } + pathParts := strings.Split(strings.Trim(path, "/"), "/") if len(pathParts) < 2 { return c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is invalid: expect format '/{lang}/...'") @@ -59,6 +66,7 @@ func Api_V1_GeneralPage_BottomEmbeds(l map[string]*locale.LocaleConfig, langs [] return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate div") } + go PCache.SetWithTTL(cacheKey, content, int64(len(content)), 5*time.Minute) c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8) return c.Status(fiber.StatusOK).Type("html").Send(content) } diff --git a/internal/router/api-v1-general-page-footer.go b/internal/router/api-v1-general-page-footer.go index 39b6bea..98da6bb 100644 --- a/internal/router/api-v1-general-page-footer.go +++ b/internal/router/api-v1-general-page-footer.go @@ -6,6 +6,7 @@ import ( "net/url" "slices" "strings" + "time" "github.com/SayaAndy/saya-today-web/locale" "github.com/gofiber/fiber/v2" @@ -29,6 +30,13 @@ func Api_V1_GeneralPage_Footer(l map[string]*locale.LocaleConfig, langs []string } path := urlStruct.EscapedPath() + + cacheKey := fmt.Sprintf("footer.%s", path) + if val, ok := PCache.Get(cacheKey); val != nil && ok { + c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8) + return c.Status(fiber.StatusOK).Type("html").Send(val) + } + pathParts := strings.Split(strings.Trim(path, "/"), "/") if len(pathParts) < 2 { return c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is invalid: expect format '/{lang}/...'") @@ -58,6 +66,7 @@ func Api_V1_GeneralPage_Footer(l map[string]*locale.LocaleConfig, langs []string return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate div") } + go PCache.SetWithTTL(cacheKey, content, int64(len(content)), 5*time.Minute) c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8) return c.Status(fiber.StatusOK).Type("html").Send(content) } diff --git a/internal/router/api-v1-general-page-header.go b/internal/router/api-v1-general-page-header.go index 0529ba6..1d65966 100644 --- a/internal/router/api-v1-general-page-header.go +++ b/internal/router/api-v1-general-page-header.go @@ -6,6 +6,7 @@ import ( "net/url" "slices" "strings" + "time" "github.com/SayaAndy/saya-today-web/internal/b2" "github.com/SayaAndy/saya-today-web/locale" @@ -30,6 +31,13 @@ func Api_V1_GeneralPage_Header(l map[string]*locale.LocaleConfig, langs []string } path := urlStruct.EscapedPath() + + cacheKey := fmt.Sprintf("header.%s", path) + if val, ok := PCache.Get(cacheKey); val != nil && ok { + c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8) + return c.Status(fiber.StatusOK).Type("html").Send(val) + } + pathParts := strings.Split(strings.Trim(path, "/"), "/") if len(pathParts) < 2 { return c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is invalid: expect format '/{lang}/...'") @@ -67,6 +75,7 @@ func Api_V1_GeneralPage_Header(l map[string]*locale.LocaleConfig, langs []string return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate div") } + go PCache.SetWithTTL(cacheKey, content, int64(len(content)), 5*time.Minute) c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8) return c.Status(fiber.StatusOK).Type("html").Send(content) } diff --git a/internal/router/api-v1-general-page-top-embeds.go b/internal/router/api-v1-general-page-top-embeds.go index 090107b..b26675f 100644 --- a/internal/router/api-v1-general-page-top-embeds.go +++ b/internal/router/api-v1-general-page-top-embeds.go @@ -6,6 +6,7 @@ import ( "net/url" "slices" "strings" + "time" "github.com/SayaAndy/saya-today-web/locale" "github.com/gofiber/fiber/v2" @@ -29,6 +30,13 @@ func Api_V1_GeneralPage_TopEmbeds(l map[string]*locale.LocaleConfig, langs []str } path := urlStruct.EscapedPath() + + cacheKey := fmt.Sprintf("top-embeds.%s", path) + if val, ok := PCache.Get(cacheKey); val != nil && ok { + c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8) + return c.Status(fiber.StatusOK).Type("html").Send(val) + } + pathParts := strings.Split(strings.Trim(path, "/"), "/") if len(pathParts) < 2 { return c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is invalid: expect format '/{lang}/...'") @@ -58,6 +66,7 @@ func Api_V1_GeneralPage_TopEmbeds(l map[string]*locale.LocaleConfig, langs []str return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate div") } + go PCache.SetWithTTL(cacheKey, content, int64(len(content)), 5*time.Minute) c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8) return c.Status(fiber.StatusOK).Type("html").Send(content) } diff --git a/internal/router/client-cache.go b/internal/router/client-cache.go index 81d7641..e448fb3 100644 --- a/internal/router/client-cache.go +++ b/internal/router/client-cache.go @@ -11,11 +11,6 @@ import ( "golang.org/x/crypto/argon2" ) -type PageLike struct { - PageRef string - UserId string -} - type ClientCache struct { hashMap map[string]string hashMapMutex sync.RWMutex diff --git a/internal/router/page-cache.go b/internal/router/page-cache.go new file mode 100644 index 0000000..01e4792 --- /dev/null +++ b/internal/router/page-cache.go @@ -0,0 +1,5 @@ +package router + +import "github.com/dgraph-io/ristretto/v2" + +var PCache *ristretto.Cache[string, []byte] diff --git a/main.go b/main.go index 72eca3d..e4e5a45 100644 --- a/main.go +++ b/main.go @@ -15,6 +15,7 @@ import ( "github.com/SayaAndy/saya-today-web/internal/router" "github.com/SayaAndy/saya-today-web/internal/tailwind" "github.com/SayaAndy/saya-today-web/locale" + "github.com/dgraph-io/ristretto/v2" "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/redirect" "github.com/golang-migrate/migrate/v4" @@ -116,7 +117,17 @@ func main() { router.CCache, err = router.NewClientCache(db, []byte(cfg.Auth.Salt)) if err != nil { - slog.Error("fail to initialize cache", slog.String("error", err.Error())) + slog.Error("fail to initialize client cache", slog.String("error", err.Error())) + os.Exit(1) + } + + router.PCache, err = ristretto.NewCache(&ristretto.Config[string, []byte]{ + NumCounters: 1e6, // 1,000,000 + MaxCost: 1 << 29, // 512 MB + BufferItems: 64, // number of keys per Get buffer. + }) + if err != nil { + slog.Error("fail to initialize page cache", slog.String("error", err.Error())) os.Exit(1) } @@ -158,5 +169,6 @@ func main() { if err = db.Close(); err != nil { slog.Error("fail to close db connection", slog.String("error", err.Error())) } + router.PCache.Close() db.Close() } -- cgit v1.3.1+13 From 0802dc3a8835bc19cf994f9eb2b462df604d4262 Mon Sep 17 00:00:00 2001 From: SayaAndy Date: Thu, 4 Sep 2025 00:51:05 +0700 Subject: feat: reroute general page derivations to general page route feat: always have page body in square aspect ratio feat: change to mobile view on 1.1/1 aspect ratio (instead of 0.8/1) feat: dynamically change title with any new page feat: cache general page template --- internal/router/api-v1-general-page-body.go | 1 + internal/router/api-v1-general-page.go | 52 +++++++++++++++++++++++++++ main.go | 4 +-- static/input.css | 56 +++++++++++++++-------------- views/layouts/general-page.html | 23 ++++++------ views/pages/blog-catalogue.html | 8 ++--- views/pages/blog-page.html | 2 +- views/pages/global-map.html | 18 +++++----- views/partials/general-page-body.html | 1 + 9 files changed, 111 insertions(+), 54 deletions(-) create mode 100644 internal/router/api-v1-general-page.go (limited to 'main.go') diff --git a/internal/router/api-v1-general-page-body.go b/internal/router/api-v1-general-page-body.go index 2329c4a..cdb558a 100644 --- a/internal/router/api-v1-general-page-body.go +++ b/internal/router/api-v1-general-page-body.go @@ -108,6 +108,7 @@ func Api_V1_GeneralPage_Body(l map[string]*locale.LocaleConfig, langs []string, values["Tags"] = tagsArray values["QuerySort"] = querySort values["QueryTags"] = strings.Join(queryTags, ",") + values["Title"] = l[lang].BlogSearch.Header additionalTemplates = append(additionalTemplates, "views/pages/blog-catalogue.html") } else if len(pathParts) == 3 && pathParts[1] == "blog" { diff --git a/internal/router/api-v1-general-page.go b/internal/router/api-v1-general-page.go new file mode 100644 index 0000000..8e1387b --- /dev/null +++ b/internal/router/api-v1-general-page.go @@ -0,0 +1,52 @@ +package router + +import ( + "fmt" + "log/slog" + "slices" + "strings" + + "github.com/SayaAndy/saya-today-web/locale" + "github.com/gofiber/fiber/v2" +) + +func init() { + tm.Add("general-page", "views/layouts/general-page.html") +} + +func Api_V1_GeneralPage(l map[string]*locale.LocaleConfig, langs []string) func(c *fiber.Ctx) error { + return func(c *fiber.Ctx) error { + c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) + path := c.Path() + + pathParts := strings.Split(strings.Trim(path, "/"), "/") + if len(pathParts) < 2 { + return c.Status(fiber.ErrBadRequest.Code).SendString("url path is invalid: expect format '/{lang}/...'") + } + + lang := pathParts[0] + if !slices.Contains(langs, lang) { + return c.Status(fiber.ErrNotFound.Code).SendString(fmt.Sprintf("server does not support '%s' language... yet??", lang)) + } + + cacheKey := "general-page." + lang + if val, ok := PCache.Get(cacheKey); val != nil && ok { + c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8) + return c.Status(fiber.StatusOK).Type("html").Send(val) + } + + content, err := tm.Render("general-page", fiber.Map{ + "L": l[lang], + "Lang": lang, + "QueryString": string(c.Request().URI().QueryString()), + }) + if err != nil { + slog.Warn("failed to generate div", slog.String("path", path), slog.String("error", err.Error())) + return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate div") + } + + go PCache.Set(cacheKey, content, int64(len(content))) + c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8) + return c.Status(fiber.StatusOK).Type("html").Send(content) + } +} diff --git a/main.go b/main.go index e4e5a45..2f8ef4b 100644 --- a/main.go +++ b/main.go @@ -133,8 +133,8 @@ func main() { app.Get("/", router.Root(cfg.AvailableLanguages)) app.Get("/:lang/map", router.Lang_Map(localization, availableLanguages, b2Client)) - app.Get("/:lang/blog", router.Lang_Blog(localization, availableLanguages, b2Client)) - app.Get("/:lang/blog/:title", router.Lang_Blog_Title(localization, availableLanguages, b2Client, md)) + app.Get("/:lang/blog", router.Api_V1_GeneralPage(localization, availableLanguages)) + app.Get("/:lang/blog/:title", router.Api_V1_GeneralPage(localization, availableLanguages)) app.Get("/api/v1/tz", router.Api_V1_TZ()) app.Get("/api/v1/blog-search", router.Api_V1_BlogSearch(localization, availableLanguages, b2Client)) diff --git a/static/input.css b/static/input.css index a64c59d..261aeb3 100644 --- a/static/input.css +++ b/static/input.css @@ -482,37 +482,41 @@ body[data-theme~="ram"] .leaflet-tile { filter: invert(1) hue-rotate(190deg) contrast(1.1) brightness(1.2) !important; } -@media (min-aspect-ratio: 0.8/1) { - .ar-gt-0\.8\:mr-\[1\.2vmin\] { +@media (min-aspect-ratio: 1.1/1) { + .ar-gt-1\.1\:mr-\[1\.2vmin\] { margin-right: 1.2vmin; } - .ar-gt-0\.8\:w-fit { + .ar-gt-1\.1\:w-fit { width: fit-content; } - .ar-gt-0\.8\:min-w-fit { + .ar-gt-1\.1\:w-\[100vh\] { + width: 100vh; + } + + .ar-gt-1\.1\:min-w-fit { min-width: fit-content; } - .ar-gt-0\.8\:min-w-\[10vh\] { + .ar-gt-1\.1\:min-w-\[10vh\] { min-width: 10vh; } - .ar-gt-0\.8\:max-w-\[20vh\] { + .ar-gt-1\.1\:max-w-\[20vh\] { max-width: 20vh; } - .ar-gt-0\.8\:max-w-\[100vh\] { + .ar-gt-1\.1\:max-w-\[100vh\] { max-width: 100vh; } - .ar-gt-0\.8\:items-center { + .ar-gt-1\.1\:items-center { align-items: center; } } -@media (max-aspect-ratio: 0.8/1) { +@media (max-aspect-ratio: 1.1/1) { .bg-sidebar { background-size: calc(var(--squares-and-triangles-background-width-coef) * 1.6vh) calc(var(--squares-and-triangles-background-height-coef) * 1.6vh); } @@ -542,71 +546,71 @@ body[data-theme~="ram"] .leaflet-tile { background-size: 10vh; } - .ar-lt-0\.8\:grid { + .ar-lt-1\.1\:grid { display: grid; } - .ar-lt-0\.8\:flex-col { + .ar-lt-1\.1\:flex-col { flex-direction: column; } - .ar-lt-0\.8\:flex-row { + .ar-lt-1\.1\:flex-row { flex-direction: row; } - .ar-lt-0\.8\:h-\[8vh\] { + .ar-lt-1\.1\:h-\[8vh\] { height: 8vh; } - .ar-lt-0\.8\:h-\[10vh\] { + .ar-lt-1\.1\:h-\[10vh\] { height: 10vh; } - .ar-lt-0\.8\:h-\[30vh\] { + .ar-lt-1\.1\:h-\[30vh\] { height: 30vh; } - .ar-lt-0\.8\:h-\[90vh\] { + .ar-lt-1\.1\:h-\[90vh\] { height: 90vh; } - .ar-lt-0\.8\:w-\[8vh\] { + .ar-lt-1\.1\:w-\[8vh\] { width: 8vh; } - .ar-lt-0\.8\:w-\[80\%\] { + .ar-lt-1\.1\:w-\[80\%\] { width: 80%; } - .ar-lt-0\.8\:w-\[90vw\] { + .ar-lt-1\.1\:w-\[90vw\] { width: 90vw; } - .ar-lt-0\.8\:w-screen { + .ar-lt-1\.1\:w-screen { width: 100vw; } - .ar-lt-0\.8\:max-h-\[25\%\] { + .ar-lt-1\.1\:max-h-\[25\%\] { max-height: 25%; } - .ar-lt-0\.8\:max-h-\[80\%\] { + .ar-lt-1\.1\:max-h-\[80\%\] { max-height: 80%; } - .ar-lt-0\.8\:max-h-\[calc\(100\%-25vh\)\] { + .ar-lt-1\.1\:max-h-\[calc\(100\%-25vh\)\] { max-height: calc(100% - 25vh); } - .ar-lt-0\.8\:text-\[5vh\] { + .ar-lt-1\.1\:text-\[5vh\] { font-size: 5vh; } - .ar-lt-0\.8\:ml-\[5vw\] { + .ar-lt-1\.1\:ml-\[5vw\] { margin-left: 5vw; } - .ar-lt-0\.8\:hidden { + .ar-lt-1\.1\:hidden { display: none; } } diff --git a/views/layouts/general-page.html b/views/layouts/general-page.html index 410e6e7..cb85a09 100644 --- a/views/layouts/general-page.html +++ b/views/layouts/general-page.html @@ -3,7 +3,6 @@ - SAYA TODAY // {{ .Title }} @@ -15,7 +14,7 @@ - + -