summaryrefslogtreecommitdiff
path: root/internal/router
diff options
from:
to:
context:
space:
mode:
authorGravatar Saya Andy <145215889+SayaAndy@users.noreply.github.com> 2026-05-18 01:33:14 +0700
committerGravatar GitHub <noreply@github.com> 2026-05-18 01:33:14 +0700
commit2c9d7e7c3110153c025c5324e0c4cc28d58cd2c1 (patch)
tree7d091ef937b8389d72ca3b0d28a6abaefb9c083e /internal/router
parent55f0ca98d42b3d540870dc1c9430b9a26ff19807 (diff)
parenta43cbea2e501371390709005055c019459554bb2 (diff)
downloadweb-0.16.0.tar.gz
web-0.16.0.zip
v0.16.0 (#40)v0.16.0
* [feat: all color themes now have light & dark variations depending on preference](https://github.com/SayaAndy/saya-today-web/commit/44b72cbc80fb89788470bda75eeb9c0bb3dee2f5) * [feat: use self-hosted pmtiles for map](https://github.com/SayaAndy/saya-today-web/commit/fa52ed00e8ef3303f14d7c953f1591eb19042635) * [feat: subheader for blog pages with medley-related ones](https://github.com/SayaAndy/saya-today-web/commit/c07da125bc5b0bb65f58c4edce912874e46f6ed8) * feat: color all leaflet elements depending on color theme & scheme * [feat: combined map widget](https://github.com/SayaAndy/saya-today-web/pull/40/commits/6418da32d978a8b70d38f1911a154e4dcc33202e) * feat: migrate static & photos to gcore cdn * [refactor: rewrite localization for dynamic set and get](https://github.com/SayaAndy/saya-today-web/commit/e9353a66bf51486f702eaa0f591dea54c9bd4cd1) * [feat: highlight the blog page visited earlier in blog catalogue](https://github.com/SayaAndy/saya-today-web/commit/ed476a1f0d3f970f977626688ac316f8b251d3a0) * fix: inline shadow now sticks to scrolling * [feat: take all metadata from centralized index.json](https://github.com/SayaAndy/saya-today-web/commit/1c441f5eab9a2a24fe5309178ce010f32adac1c7) * [fix: font in like button on smartphone screens](https://github.com/SayaAndy/saya-today-web/commit/551295ecacebbb94019def022f70397f74c44775) * [feat: use "page // medley" format for title](https://github.com/SayaAndy/saya-today-web/pull/40/commits/0dbb85f25c69b5e7929bb832e7967a709a76ff5c) * feat: multitone backgrounds implementation * feat: change font to m-plus for short description * fix: theme wheel position on different dpi screens in global map * feat: integrate package minor updates * refactor: convert color vars into oklch * [refactor: rm scanning by listing for s3 client](https://github.com/SayaAndy/saya-today-web/commit/1a95e20cc4e050244a76a3ea4cccc5ab30ff5a91) * fix: on errors with blog client init write correct type
Diffstat (limited to 'internal/router')
-rw-r--r--internal/router/handlers/api-v1-blog-search.go53
-rw-r--r--internal/router/handlers/api-v1-email-is-in-verification.go3
-rw-r--r--internal/router/handlers/api-v1-email-send-verification-code.go15
-rw-r--r--internal/router/handlers/api-v1-email-verify.go7
-rw-r--r--internal/router/handlers/api-v1-map-get.go116
-rw-r--r--internal/router/handlers/api-v1-subs-put.go7
-rw-r--r--internal/router/handlers/lang-blog-title.go25
-rw-r--r--internal/router/handlers/lang-blog.go16
-rw-r--r--internal/router/handlers/lang-map.go65
-rw-r--r--internal/router/handlers/lang-user-unsubscribe.go9
-rw-r--r--internal/router/handlers/lang-user.go9
-rw-r--r--internal/router/handlers/lang.go9
-rw-r--r--internal/router/router.go59
13 files changed, 233 insertions, 160 deletions
diff --git a/internal/router/handlers/api-v1-blog-search.go b/internal/router/handlers/api-v1-blog-search.go
index 38004a1..b59f09a 100644
--- a/internal/router/handlers/api-v1-blog-search.go
+++ b/internal/router/handlers/api-v1-blog-search.go
@@ -1,13 +1,13 @@
package handlers
import (
+ "cmp"
"encoding/json"
"fmt"
"log/slog"
"net/url"
"regexp"
"slices"
- "strings"
"time"
"github.com/SayaAndy/saya-today-web/internal/blog"
@@ -51,6 +51,10 @@ func (r *BlogSearchHandler) RateLimiter() *fiber.Handler {
func (r *BlogSearchHandler) Render(c *fiber.Ctx, supplements *router.Supplements, lang string, templateMap fiber.Map) (statusCode int, err error) {
sort := c.Query("sort")
tz := c.Query("tz")
+ medley := c.Query("medley")
+ highlight := c.Query("highlight")
+ hideTags := c.QueryBool("hideTags", false)
+ hidePublishedTime := c.QueryBool("hidePublishedTime", false)
loc, err := time.LoadLocation(tz)
if err != nil {
@@ -85,21 +89,26 @@ func (r *BlogSearchHandler) Render(c *fiber.Ctx, supplements *router.Supplements
for _, page := range pages {
for _, tag := range page.Metadata.Tags {
if len(tags) == 0 || slices.Contains(tags, tag) {
- pageMeta = append(pageMeta, fiber.Map{
- "Link": page.Link,
- "ArticleLink": "/" + lang + "/blog/" + page.FileName,
- "Title": page.Metadata.Title,
- "PublishedTime": page.Metadata.PublishedTime.In(loc).Format("2006-01-02 15:04:05 -07:00"),
- "ActionDate": page.Metadata.ActionDate,
- "ShortDescription": page.Metadata.ShortDescription,
- "Thumbnail": page.Metadata.Thumbnail,
- "Tags": page.Metadata.Tags,
- "LikeCount": supplements.ClientCache.GetLikeCount(page.FileName),
- "Liked": supplements.ClientCache.GetLikeStatus(c.IP(), page.FileName),
- "ViewCount": supplements.ClientCache.GetViewCount(page.FileName),
- "Viewed": supplements.ClientCache.GetViewStatus(c.IP(), page.FileName),
- })
- break
+ if medley == "" || medley == page.Metadata.Medley {
+ pageMeta = append(pageMeta, fiber.Map{
+ "Link": page.Link,
+ "ArticleLink": "/" + lang + "/blog/" + page.FileName,
+ "Title": page.Metadata.Title,
+ "PublishedTime": page.Metadata.PublishedTime.In(loc).Format("2006-01-02 15:04:05 -07:00"),
+ "ActionDate": page.Metadata.ActionDate,
+ "ShortDescription": page.Metadata.ShortDescription,
+ "Thumbnail": page.Metadata.Thumbnail,
+ "Tags": page.Metadata.Tags,
+ "LikeCount": supplements.ClientCache.GetLikeCount(page.FileName),
+ "Liked": supplements.ClientCache.GetLikeStatus(c.IP(), page.FileName),
+ "ViewCount": supplements.ClientCache.GetViewCount(page.FileName),
+ "Viewed": supplements.ClientCache.GetViewStatus(c.IP(), page.FileName),
+ "Medley": page.Metadata.Medley,
+ "MedleyPart": page.Metadata.MedleyPart,
+ "ToHighlight": page.FileName == highlight,
+ })
+ break
+ }
}
}
}
@@ -107,13 +116,13 @@ func (r *BlogSearchHandler) Render(c *fiber.Ctx, supplements *router.Supplements
slices.SortFunc(pageMeta, func(a, b fiber.Map) int {
switch sort {
case "titleAsc":
- return strings.Compare(a["Title"].(string), b["Title"].(string))
+ return cmp.Compare(a["Title"].(string), b["Title"].(string))
case "titleDesc":
- return strings.Compare(b["Title"].(string), a["Title"].(string))
+ return cmp.Compare(b["Title"].(string), a["Title"].(string))
case "actionDateAsc":
- return strings.Compare(a["ActionDate"].(string), b["ActionDate"].(string))
+ return cmp.Compare(a["ActionDate"].(string), b["ActionDate"].(string))
case "actionDateDesc":
- return strings.Compare(b["ActionDate"].(string), a["ActionDate"].(string))
+ return cmp.Compare(b["ActionDate"].(string), a["ActionDate"].(string))
case "publicationDateAsc":
publishedTimeA, _ := time.Parse("2006-01-02 15:04:05 -07:00", a["PublishedTime"].(string))
publishedTimeB, _ := time.Parse("2006-01-02 15:04:05 -07:00", b["PublishedTime"].(string))
@@ -122,11 +131,15 @@ func (r *BlogSearchHandler) Render(c *fiber.Ctx, supplements *router.Supplements
publishedTimeA, _ := time.Parse("2006-01-02 15:04:05 -07:00", a["PublishedTime"].(string))
publishedTimeB, _ := time.Parse("2006-01-02 15:04:05 -07:00", b["PublishedTime"].(string))
return publishedTimeB.Compare(publishedTimeA)
+ case "medley":
+ return cmp.Compare(a["MedleyPart"].(int), b["MedleyPart"].(int))
}
return 0
})
templateMap["BlogPages"] = pageMeta
+ templateMap["HideTags"] = hideTags
+ templateMap["HidePublishedTime"] = hidePublishedTime
return fiber.StatusOK, nil
}
diff --git a/internal/router/handlers/api-v1-email-is-in-verification.go b/internal/router/handlers/api-v1-email-is-in-verification.go
index 4f05f48..b8ae162 100644
--- a/internal/router/handlers/api-v1-email-is-in-verification.go
+++ b/internal/router/handlers/api-v1-email-is-in-verification.go
@@ -6,6 +6,7 @@ import (
"strings"
"github.com/SayaAndy/saya-today-web/internal/router"
+ "github.com/SayaAndy/saya-today-web/l10n"
"github.com/gofiber/fiber/v2"
)
@@ -49,7 +50,7 @@ func (r *OngoingVerificationHandler) Render(c *fiber.Ctx, supplements *router.Su
if !isAllowed {
sterileDataset["striked-end-time"] = template.HTMLAttr(fmt.Sprintf("data-striked-end-time=\"%d\"", whenAllowed.UnixMilli()))
templateMap["Status"] = "Neutral"
- templateMap["Message"] = strings.ReplaceAll(supplements.Localization[lang].UserProfile.DelayTilVerification, "{}", whenAllowed.Format("2006-01-02 15:04:05 MST"))
+ templateMap["Message"] = strings.ReplaceAll(l10n.T.GetPath(lang, "UserProfile", "DelayTilVerification").(string), "{}", whenAllowed.Format("2006-01-02 15:04:05 MST"))
} else {
templateMap["Status"] = "OK"
templateMap["Message"] = ""
diff --git a/internal/router/handlers/api-v1-email-send-verification-code.go b/internal/router/handlers/api-v1-email-send-verification-code.go
index 0b53efa..ea8df4d 100644
--- a/internal/router/handlers/api-v1-email-send-verification-code.go
+++ b/internal/router/handlers/api-v1-email-send-verification-code.go
@@ -7,6 +7,7 @@ import (
"strings"
"github.com/SayaAndy/saya-today-web/internal/router"
+ "github.com/SayaAndy/saya-today-web/l10n"
"github.com/gofiber/fiber/v2"
)
@@ -49,27 +50,27 @@ func (r *SendVerificationCodeHandler) Render(c *fiber.Ctx, supplements *router.S
email := c.FormValue("email")
if email == "" {
templateMap["Status"] = "Failed"
- templateMap["Message"] = supplements.Localization[lang].UserProfile.EmailEmpty
+ templateMap["Message"] = l10n.T.GetPath(lang, "UserProfile", "EmailEmpty").(string)
return fiber.StatusUnprocessableEntity, nil
}
isTaken, err := supplements.Mailer.MailIsTaken(email)
if err != nil {
templateMap["Status"] = "Failed"
- templateMap["Message"] = supplements.Localization[lang].UserProfile.VerificationCodeSendingError
+ templateMap["Message"] = l10n.T.GetPath(lang, "UserProfile", "VerificationCodeSendingError").(string)
slog.Error("failed to check if address is already taken", slog.String("error", err.Error()))
return fiber.StatusUnprocessableEntity, nil
}
if isTaken {
templateMap["Status"] = "Failed"
- templateMap["Message"] = supplements.Localization[lang].UserProfile.EmailTaken
+ templateMap["Message"] = l10n.T.GetPath(lang, "UserProfile", "EmailTaken").(string)
return fiber.StatusUnprocessableEntity, nil
}
if isAllowed, whenAllowed, _ := supplements.Mailer.IsAllowedToRetryVerification(id); !isAllowed {
templateMap["Status"] = "Failed"
- templateMap["Message"] = strings.ReplaceAll(supplements.Localization[lang].UserProfile.DelayTilVerification, "{}", whenAllowed.Format("2006-01-02 15:04:05 MST"))
+ templateMap["Message"] = strings.ReplaceAll(l10n.T.GetPath(lang, "UserProfile", "DelayTilVerification").(string), "{}", whenAllowed.Format("2006-01-02 15:04:05 MST"))
templateMap["DataAttributes"] = map[string]any{
"striked-end-time": template.HTMLAttr(fmt.Sprintf("data-striked-end-time=\"%d\"", whenAllowed.UnixMilli())),
}
@@ -78,20 +79,20 @@ func (r *SendVerificationCodeHandler) Render(c *fiber.Ctx, supplements *router.S
if previousEmail, _, _ := supplements.Mailer.GetInfo(supplements.Mailer.GetHash(id)); previousEmail == email {
templateMap["Status"] = "Failed"
- templateMap["Message"] = supplements.Localization[lang].UserProfile.EmailAlreadyValidated
+ templateMap["Message"] = l10n.T.GetPath(lang, "UserProfile", "EmailAlreadyValidated").(string)
return fiber.StatusUnprocessableEntity, nil
}
if err = supplements.Mailer.SendVerificationCode(id, email, lang); err != nil {
templateMap["Status"] = "Failed"
- templateMap["Message"] = supplements.Localization[lang].UserProfile.VerificationCodeSendingError
+ templateMap["Message"] = l10n.T.GetPath(lang, "UserProfile", "VerificationCodeSendingError").(string)
slog.Error("failed to send a verification code", slog.String("error", err.Error()))
return fiber.StatusUnprocessableEntity, nil
}
_, endTime, codeExpiry := supplements.Mailer.IsAllowedToRetryVerification(id)
templateMap["Status"] = "OK"
- templateMap["Message"] = supplements.Localization[lang].UserProfile.VerificationCodeSent
+ templateMap["Message"] = l10n.T.GetPath(lang, "UserProfile", "VerificationCodeSent").(string)
templateMap["DataAttributes"] = map[string]any{
"striked-end-time": template.HTMLAttr(fmt.Sprintf("data-striked-end-time=\"%d\"", endTime.UnixMilli())),
"code-expiry-time": template.HTMLAttr(fmt.Sprintf("data-code-expiry-time=\"%d\"", codeExpiry.UnixMilli())),
diff --git a/internal/router/handlers/api-v1-email-verify.go b/internal/router/handlers/api-v1-email-verify.go
index ace1870..65ad6d9 100644
--- a/internal/router/handlers/api-v1-email-verify.go
+++ b/internal/router/handlers/api-v1-email-verify.go
@@ -5,6 +5,7 @@ import (
"log/slog"
"github.com/SayaAndy/saya-today-web/internal/router"
+ "github.com/SayaAndy/saya-today-web/l10n"
"github.com/gofiber/fiber/v2"
)
@@ -46,19 +47,19 @@ func (r *VerifyCodeHandler) Render(c *fiber.Ctx, supplements *router.Supplements
if verificationCode == "" {
templateMap["Status"] = "Failed"
- templateMap["Message"] = supplements.Localization[lang].UserProfile.VerificationEmpty
+ templateMap["Message"] = l10n.T.GetPath(lang, "UserProfile", "VerificationEmpty").(string)
return fiber.StatusUnprocessableEntity, nil
}
if err = supplements.Mailer.Verify(verificationCode, lang); err != nil {
slog.Warn("verification code is invalid", slog.String("verification_code", verificationCode), slog.String("error", err.Error()))
templateMap["Status"] = "Failed"
- templateMap["Message"] = supplements.Localization[lang].UserProfile.VerificationFailed
+ templateMap["Message"] = l10n.T.GetPath(lang, "UserProfile", "VerificationFailed").(string)
return fiber.StatusUnprocessableEntity, nil
}
templateMap["Status"] = "OK"
- templateMap["Message"] = supplements.Localization[lang].UserProfile.VerificationSuccess
+ templateMap["Message"] = l10n.T.GetPath(lang, "UserProfile", "VerificationSuccess").(string)
templateMap["DataAttributes"] = map[string]any{
"hide-verification-panel": template.HTMLAttr("data-code-expiry-time=\"true\""),
}
diff --git a/internal/router/handlers/api-v1-map-get.go b/internal/router/handlers/api-v1-map-get.go
new file mode 100644
index 0000000..e19c54a
--- /dev/null
+++ b/internal/router/handlers/api-v1-map-get.go
@@ -0,0 +1,116 @@
+package handlers
+
+import (
+ "fmt"
+ "log/slog"
+ "slices"
+ "strconv"
+ "strings"
+
+ "github.com/SayaAndy/saya-today-web/internal/blog"
+ "github.com/SayaAndy/saya-today-web/internal/router"
+ "github.com/gofiber/fiber/v2"
+)
+
+type GetMapHandler struct {
+ router.BasicHandler
+}
+
+func init() {
+ router.Routes = append(router.Routes, &GetMapHandler{})
+}
+
+func (r *GetMapHandler) Filter() (method string, path string) {
+ return "GET", "/api/v1/map"
+}
+
+func (r *GetMapHandler) IsTemplated() bool {
+ return false
+}
+
+func (r *GetMapHandler) TemplatesToInject() []string {
+ return []string{"views/partials/global-map-widget.html"}
+}
+
+func (r *GetMapHandler) ToCache() router.CacheSetting {
+ return router.ByUrlAndQuery
+}
+
+func (r *GetMapHandler) ToValidateLang() router.LangSetting {
+ return router.InReferer
+}
+
+func (r *GetMapHandler) Render(c *fiber.Ctx, supplements *router.Supplements, lang string, templateMap fiber.Map) (statusCode int, err error) {
+ codename := c.Query("codename")
+ zoom := c.QueryInt("zoom", 4)
+ zoomPosition := c.Query("zoomPosition")
+
+ pages, err := supplements.BlogClient.Scan(lang + "/")
+ status := fiber.StatusOK
+ if err != nil {
+ slog.Error("received an error while scanning blog pages",
+ slog.String("error", err.Error()),
+ slog.String("lang", lang),
+ )
+ status = fiber.StatusPartialContent
+ pages = []*blog.Page{}
+ }
+ slices.SortFunc(pages, func(a *blog.Page, b *blog.Page) int {
+ return a.Metadata.PublishedTime.Compare(b.Metadata.PublishedTime)
+ })
+
+ type MapMarker struct {
+ Index int `json:"Index"`
+ Title string `json:"Title"`
+ PageLink string `json:"PageLink"`
+ Lat float64 `json:"Lat"`
+ Long float64 `json:"Long"`
+ AccuracyMeters int64 `json:"AccuracyMeters"`
+ Thumbnail string `json:"Thumbnail"`
+ ToHighlight bool `json:"ToHighlight"`
+ }
+
+ templateMap["MapLocationLat"] = 45.4507
+ templateMap["MapLocationLong"] = 68.8319
+ templateMap["MapLocationZoom"] = zoom
+ templateMap["ZoomPosition"] = zoomPosition
+
+ mapMarkers := make([]*MapMarker, 0, len(pages))
+ for i, page := range pages {
+ geolocationParts := strings.Split(page.Metadata.Geolocation, " ")
+ if len(geolocationParts) < 2 {
+ continue
+ }
+
+ var x, y float64
+ var areaError int64
+ if len(geolocationParts) >= 2 {
+ x, _ = strconv.ParseFloat(geolocationParts[0], 64)
+ y, _ = strconv.ParseFloat(geolocationParts[1], 64)
+ }
+ if len(geolocationParts) >= 3 {
+ areaError, _ = strconv.ParseInt(geolocationParts[2], 10, 64)
+ }
+
+ toHighlight := page.FileName == codename
+ if toHighlight {
+ templateMap["MapLocationLat"] = x
+ templateMap["MapLocationLong"] = y
+ }
+
+ mapMarkers = append(mapMarkers, &MapMarker{
+ Index: i,
+ Title: page.Metadata.Title,
+ PageLink: fmt.Sprintf("/%s/blog/%s", lang, page.FileName),
+ Lat: x,
+ Long: y,
+ AccuracyMeters: areaError,
+ Thumbnail: page.Metadata.Thumbnail,
+ ToHighlight: toHighlight,
+ })
+ }
+
+ templateMap["MapMarkers"] = mapMarkers
+
+ return status, nil
+}
diff --git a/internal/router/handlers/api-v1-subs-put.go b/internal/router/handlers/api-v1-subs-put.go
index 32fba7e..12dc5cf 100644
--- a/internal/router/handlers/api-v1-subs-put.go
+++ b/internal/router/handlers/api-v1-subs-put.go
@@ -3,6 +3,7 @@ package handlers
import (
"github.com/SayaAndy/saya-today-web/internal/mailer"
"github.com/SayaAndy/saya-today-web/internal/router"
+ "github.com/SayaAndy/saya-today-web/l10n"
"github.com/gofiber/fiber/v2"
)
@@ -52,18 +53,18 @@ func (r *PutSubsHandler) Render(c *fiber.Ctx, supplements *router.Supplements, l
subscriptionTypeEnum = mailer.Specific
default:
templateMap["Status"] = "Failed"
- templateMap["Message"] = supplements.Localization[lang].UserProfile.SubscribeInvalidType
+ templateMap["Message"] = l10n.T.GetPath(lang, "UserProfile", "SubscribeInvalidType").(string)
return fiber.StatusUnprocessableEntity, nil
}
specificTags := c.FormValue("tags_picked")
if err = supplements.Mailer.Subscribe(supplements.Mailer.GetHash(c.IP()), subscriptionTypeEnum, specificTags); err != nil {
templateMap["Status"] = "Failed"
- templateMap["Message"] = supplements.Localization[lang].UserProfile.FailedToSubscribe
+ templateMap["Message"] = l10n.T.GetPath(lang, "UserProfile", "FailedToSubscribe").(string)
return fiber.StatusUnprocessableEntity, nil
}
templateMap["Status"] = "OK"
- templateMap["Message"] = supplements.Localization[lang].UserProfile.SubscribedSuccessfully
+ templateMap["Message"] = l10n.T.GetPath(lang, "UserProfile", "SubscribedSuccessfully").(string)
return fiber.StatusOK, nil
}
diff --git a/internal/router/handlers/lang-blog-title.go b/internal/router/handlers/lang-blog-title.go
index 49744c1..5a1155d 100644
--- a/internal/router/handlers/lang-blog-title.go
+++ b/internal/router/handlers/lang-blog-title.go
@@ -10,6 +10,7 @@ import (
"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/SayaAndy/saya-today-web/l10n"
"github.com/gofiber/fiber/v2"
"github.com/yuin/goldmark"
)
@@ -70,11 +71,15 @@ func (r *BlogPageHandler) AddMeta(c *fiber.Ctx, supplements *router.Supplements,
if err != nil {
return nil, fmt.Errorf("failed to read frontmatter of the desired blog post: %w", err)
}
+ title := metadata.Title
+ if metadata.Medley != "" {
+ title += " // " + l10n.T.GetPath(lang, "Medleys", metadata.Medley).(string)
+ }
return []router.MetaField{
- {Property: "og:title", Content: metadata.Title},
+ {Property: "og:title", Content: 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:image", Content: fmt.Sprintf(supplements.PhotoStorage.Thumbnail560p.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"},
@@ -86,11 +91,15 @@ func (r *BlogPageHandler) AddLinkedData(c *fiber.Ctx, supplements *router.Supple
if err != nil {
return nil, fmt.Errorf("failed to read frontmatter of the desired blog post: %w", err)
}
+ title := metadata.Title
+ if metadata.Medley != "" {
+ title += " // " + l10n.T.GetPath(lang, "Medleys", metadata.Medley).(string)
+ }
return map[string]any{
"@context": "https://schema.org",
"@type": "Article",
- "headline": metadata.Title,
+ "headline": 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),
@@ -102,10 +111,11 @@ func (r *BlogPageHandler) RenderBody(c *fiber.Ctx, supplements *router.Supplemen
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+"/"+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", pathParts[2], err)
+ return fiber.StatusNotFound, fmt.Errorf("failed to find '%s' post: %w", title, err)
}
geolocationParts := strings.Split(metadata.Geolocation, " ")
@@ -122,12 +132,15 @@ func (r *BlogPageHandler) RenderBody(c *fiber.Ctx, supplements *router.Supplemen
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(), pathParts[2])
+ go supplements.ClientCache.View(c.IP(), title)
return fiber.StatusOK, nil
}
diff --git a/internal/router/handlers/lang-blog.go b/internal/router/handlers/lang-blog.go
index 79ec9f3..c31c2eb 100644
--- a/internal/router/handlers/lang-blog.go
+++ b/internal/router/handlers/lang-blog.go
@@ -11,6 +11,7 @@ import (
"github.com/SayaAndy/saya-today-web/internal/blog"
"github.com/SayaAndy/saya-today-web/internal/router"
+ "github.com/SayaAndy/saya-today-web/l10n"
"github.com/gofiber/fiber/v2"
)
@@ -57,18 +58,16 @@ func (r *CatalogueHandler) SitemapInfo(supplements *router.Supplements) []router
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:title", Content: l10n.T.GetPath(lang, "BlogSearch", "Header").(string)},
+ {Property: "og:description", Content: l10n.T.GetPath(lang, "BlogSearch", "Description").(string)},
{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 == "" {
- querySort = "publicationDateDesc"
- }
+ querySort := c.Query("sort", "publicationDateDesc")
+ previousBlogPage := c.Query("codename")
encodedQuery := c.Request().URI().QueryString()
decodedQuery, _ := url.QueryUnescape(string(encodedQuery))
@@ -87,13 +86,14 @@ func (r *CatalogueHandler) RenderBody(c *fiber.Ctx, supplements *router.Suppleme
templateMap["Tags"] = tagsArray
templateMap["QuerySort"] = querySort
templateMap["QueryTags"] = strings.Join(queryTags, ",")
- templateMap["Title"] = supplements.Localization[lang].BlogSearch.Header
+ templateMap["Title"] = l10n.T.GetPath(lang, "BlogSearch", "Header").(string)
+ templateMap["PreviousBlogPage"] = previousBlogPage
return fiber.StatusOK, nil
}
func (r *CatalogueHandler) RenderHeader(c *fiber.Ctx, supplements *router.Supplements, lang string, templateMap fiber.Map) (statusCode int, err error) {
- templateMap["Title"] = supplements.Localization[lang].BlogSearch.Header
+ templateMap["Title"] = l10n.T.GetPath(lang, "BlogSearch", "Header").(string)
return fiber.StatusOK, nil
}
diff --git a/internal/router/handlers/lang-map.go b/internal/router/handlers/lang-map.go
index d452bc7..32ab35e 100644
--- a/internal/router/handlers/lang-map.go
+++ b/internal/router/handlers/lang-map.go
@@ -1,13 +1,6 @@
package handlers
import (
- "fmt"
- "log/slog"
- "slices"
- "strconv"
- "strings"
-
- "github.com/SayaAndy/saya-today-web/internal/blog"
"github.com/SayaAndy/saya-today-web/internal/router"
"github.com/gofiber/fiber/v2"
)
@@ -49,61 +42,5 @@ func (r *MapHandler) SitemapInfo(supplements *router.Supplements) []router.Sitem
}
func (r *MapHandler) Render(c *fiber.Ctx, supplements *router.Supplements, lang string, templateMap fiber.Map) (statusCode int, err error) {
- pages, err := supplements.BlogClient.Scan(lang + "/")
- slices.SortFunc(pages, func(a *blog.Page, b *blog.Page) int {
- return a.Metadata.PublishedTime.Compare(b.Metadata.PublishedTime)
- })
- status := fiber.StatusOK
- if err != nil {
- slog.Error("received an error while scanning b2 pages",
- slog.String("error", err.Error()),
- slog.String("lang", lang),
- )
- status = fiber.StatusPartialContent
- pages = []*blog.Page{}
- }
-
- type MapMarker struct {
- Index int `json:"Index"`
- Title string `json:"Title"`
- PageLink string `json:"PageLink"`
- Lat float64 `json:"Lat"`
- Long float64 `json:"Long"`
- AccuracyMeters int64 `json:"AccuracyMeters"`
- Thumbnail string `json:"Thumbnail"`
- }
-
- mapMarkers := make([]*MapMarker, 0, len(pages))
- for i, page := range pages {
- geolocationParts := strings.Split(page.Metadata.Geolocation, " ")
- if len(geolocationParts) < 2 {
- continue
- }
-
- var x, y float64
- var areaError int64
- if len(geolocationParts) >= 2 {
- x, _ = strconv.ParseFloat(geolocationParts[0], 64)
- y, _ = strconv.ParseFloat(geolocationParts[1], 64)
- }
- if len(geolocationParts) >= 3 {
- areaError, _ = strconv.ParseInt(geolocationParts[2], 10, 64)
- }
-
- mapMarkers = append(mapMarkers, &MapMarker{
- Index: i,
- Title: page.Metadata.Title,
- PageLink: fmt.Sprintf("/%s/blog/%s", lang, page.FileName),
- Lat: x,
- Long: y,
- AccuracyMeters: areaError,
- Thumbnail: page.Metadata.Thumbnail,
- })
- }
-
- templateMap["MapMarkers"] = mapMarkers
- templateMap["MapLocationLat"] = 45.4507
- templateMap["MapLocationLong"] = 68.8319
-
- return status, nil
+ return fiber.StatusOK, nil
}
diff --git a/internal/router/handlers/lang-user-unsubscribe.go b/internal/router/handlers/lang-user-unsubscribe.go
index 4fbd244..e22d854 100644
--- a/internal/router/handlers/lang-user-unsubscribe.go
+++ b/internal/router/handlers/lang-user-unsubscribe.go
@@ -4,6 +4,7 @@ import (
"log/slog"
"github.com/SayaAndy/saya-today-web/internal/router"
+ "github.com/SayaAndy/saya-today-web/l10n"
"github.com/gofiber/fiber/v2"
)
@@ -39,24 +40,24 @@ func (r *UnsubscribeHandler) Render(c *fiber.Ctx, supplements *router.Supplement
if unsubscribeCode == "" {
statusColor = "0, 0, 255"
statusEmoji = "(╭ರ_•́)"
- statusText = supplements.Localization[lang].UnsubscribePage.UnsetCode
+ statusText = l10n.T.GetPath(lang, "UnsubscribePage", "UnsetCode").(string)
status = fiber.ErrBadRequest.Code
} else if clientError, serverError := supplements.Mailer.Unsubscribe(unsubscribeCode); clientError != nil {
slog.Info("got a client error when unsubscribing", slog.String("error", clientError.Error()))
statusColor = "255, 0, 0"
statusEmoji = "(͠≖~≖ ͡ )"
- statusText = supplements.Localization[lang].UnsubscribePage.InvalidCode
+ statusText = l10n.T.GetPath(lang, "UnsubscribePage", "InvalidCode").(string)
status = fiber.ErrBadRequest.Code
} else if serverError != nil {
slog.Error("got a server error when unsubscribing", slog.String("error", serverError.Error()))
statusColor = "255, 128, 0"
statusEmoji = "( ˶°ㅁ°) !!"
- statusText = supplements.Localization[lang].UnsubscribePage.OnServerError
+ statusText = l10n.T.GetPath(lang, "UnsubscribePage", "OnServerError").(string)
status = fiber.ErrInternalServerError.Code
} else {
statusColor = "0, 255, 0"
statusEmoji = "♡⸜(˶˃ ᵕ ˂˶)⸝♡"
- statusText = supplements.Localization[lang].UnsubscribePage.Success
+ statusText = l10n.T.GetPath(lang, "UnsubscribePage", "Success").(string)
status = fiber.StatusOK
}
diff --git a/internal/router/handlers/lang-user.go b/internal/router/handlers/lang-user.go
index 0dda529..085a9aa 100644
--- a/internal/router/handlers/lang-user.go
+++ b/internal/router/handlers/lang-user.go
@@ -6,6 +6,7 @@ import (
"github.com/SayaAndy/saya-today-web/internal/mailer"
"github.com/SayaAndy/saya-today-web/internal/router"
+ "github.com/SayaAndy/saya-today-web/l10n"
"github.com/gofiber/fiber/v2"
)
@@ -40,15 +41,15 @@ func (r *UserHandler) ToValidateLang() router.LangSetting {
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:title", Content: l10n.T.GetPath(lang, "UserProfile", "Header").(string)},
+ {Property: "og:description", Content: l10n.T.GetPath(lang, "UserProfile", "Description").(string)},
{Property: "og:url", Content: fmt.Sprintf("%s/%s/user", templateMap["CanonicalEndpoint"], lang)},
{Property: "og:type", Content: "website"},
}, nil
}
func (r *UserHandler) RenderBody(c *fiber.Ctx, supplements *router.Supplements, lang string, templateMap fiber.Map) (statusCode int, err error) {
- templateMap["Title"] = supplements.Localization[lang].UserProfile.Header
+ templateMap["Title"] = l10n.T.GetPath(lang, "UserProfile", "Header").(string)
email, _, err := supplements.Mailer.GetInfo(supplements.Mailer.GetHash(c.IP()))
if err != nil {
@@ -83,6 +84,6 @@ func (r *UserHandler) RenderBody(c *fiber.Ctx, supplements *router.Supplements,
}
func (r *UserHandler) RenderHeader(c *fiber.Ctx, supplements *router.Supplements, lang string, templateMap fiber.Map) (statusCode int, err error) {
- templateMap["Title"] = supplements.Localization[lang].UserProfile.Header
+ templateMap["Title"] = l10n.T.GetPath(lang, "UserProfile", "Header").(string)
return fiber.StatusOK, nil
}
diff --git a/internal/router/handlers/lang.go b/internal/router/handlers/lang.go
index 25b5036..cc0b33b 100644
--- a/internal/router/handlers/lang.go
+++ b/internal/router/handlers/lang.go
@@ -6,6 +6,7 @@ import (
"time"
"github.com/SayaAndy/saya-today-web/internal/router"
+ "github.com/SayaAndy/saya-today-web/l10n"
"github.com/gofiber/fiber/v2"
)
@@ -51,8 +52,8 @@ func (r *HomeHandler) SitemapInfo(supplements *router.Supplements) []router.Site
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:title", Content: l10n.T.GetPath(lang, "HomePage", "Header").(string)},
+ {Property: "og:description", Content: l10n.T.GetPath(lang, "HomePage", "HomePageDescription").(string)},
{Property: "og:image", Content: fmt.Sprintf(
supplements.PhotoStorage.HomePageGifs.BaseUrl,
supplements.PhotoStorage.HomePageGifs.Indexes[rand.Int()%len(supplements.PhotoStorage.HomePageGifs.Indexes)],
@@ -63,7 +64,7 @@ func (r *HomeHandler) AddMeta(c *fiber.Ctx, supplements *router.Supplements, lan
}
func (r *HomeHandler) RenderBody(c *fiber.Ctx, supplements *router.Supplements, lang string, templateMap fiber.Map) (statusCode int, err error) {
- templateMap["Title"] = supplements.Localization[lang].HomePage.Header
+ templateMap["Title"] = l10n.T.GetPath(lang, "HomePage", "Header").(string)
templateMap["FilledHeartCount"] = uint(40)
templateMap["OutlineHeartCount"] = uint(40)
templateMap["GifUrl"] = fmt.Sprintf(
@@ -75,6 +76,6 @@ func (r *HomeHandler) RenderBody(c *fiber.Ctx, supplements *router.Supplements,
}
func (r *HomeHandler) RenderHeader(c *fiber.Ctx, supplements *router.Supplements, lang string, templateMap fiber.Map) (statusCode int, err error) {
- templateMap["Title"] = supplements.Localization[lang].HomePage.Header
+ templateMap["Title"] = l10n.T.GetPath(lang, "HomePage", "Header").(string)
return fiber.StatusOK, nil
}
diff --git a/internal/router/router.go b/internal/router/router.go
index 7f3511a..c8b5686 100644
--- a/internal/router/router.go
+++ b/internal/router/router.go
@@ -23,7 +23,6 @@ import (
"github.com/SayaAndy/saya-today-web/internal/mailer"
"github.com/SayaAndy/saya-today-web/internal/tailwind"
"github.com/SayaAndy/saya-today-web/internal/templatemanager"
- "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"
@@ -95,7 +94,6 @@ type Route interface {
type Supplements struct {
DB *sql.DB
BlogClient blog.Client
- Localization map[string]*locale.LocaleConfig
AvailableLanguages []config.AvailableLanguageConfig
ClientCache *ClientCache
PageCache *ristretto.Cache[string, []byte]
@@ -106,7 +104,7 @@ type Supplements struct {
MarkdownRenderer goldmark.Markdown
Meta config.MetaConfig
PhotoStorage config.PhotoStorageConfig
- StaticStorage config.PhotoTypeConfig
+ StaticStorage config.StaticStorageConfig
}
type Router struct {
@@ -149,16 +147,7 @@ func NewRouter(cfg *config.Config) (*Router, error) {
supplements.BlogClient, err = blog.NewClientMap[cfg.BlogPages.Storage.Type](&cfg.BlogPages.Storage)
if err != nil {
- return nil, fmt.Errorf("fail to initialize b2 client: %w", err)
- }
-
- supplements.Localization = make(map[string]*locale.LocaleConfig, len(cfg.AvailableLanguages))
- for _, lang := range cfg.AvailableLanguages {
- localeCfg, err := locale.InitConfig(cfg.LocalePath + lang.LocFile)
- if err != nil {
- return nil, fmt.Errorf("fail to initialize a locale: %w", err)
- }
- supplements.Localization[lang.Name] = localeCfg
+ return nil, fmt.Errorf("fail to initialize blog client: type %s: %w", cfg.BlogPages.Storage.Type, err)
}
supplements.MarkdownRenderer = goldmark.New(
@@ -200,7 +189,7 @@ func NewRouter(cfg *config.Config) (*Router, error) {
}
supplements.Mailer, err = mailer.NewMailer(supplements.DB, cfg.Mail.ClientHost, cfg.Mail.MailHost,
- cfg.Mail.PublicName, cfg.Mail.MailAddress, cfg.Mail.Username, cfg.Mail.Password, []byte(cfg.Mail.Salt), supplements.Localization)
+ cfg.Mail.PublicName, cfg.Mail.MailAddress, cfg.Mail.Username, cfg.Mail.Password, []byte(cfg.Mail.Salt))
if err != nil {
return nil, fmt.Errorf("fail to initialize mailer: %w", err)
}
@@ -314,13 +303,12 @@ func (r *Router) InitRoutes() (err error) {
}
defaultMap := fiber.Map{
- "L": r.supplements.Localization[lang],
- "Lang": lang,
- "Path": trimmedPath,
- "QueryString": queryString,
- "CanonicalEndpoint": r.canonicalEndpoint,
- "StaticStorageBaseUrl": r.supplements.StaticStorage.BaseUrl,
- "ThumbnailBaseUrl": r.supplements.PhotoStorage.Thumbnail320p.BaseUrl,
+ "Lang": lang,
+ "Path": trimmedPath,
+ "QueryString": queryString,
+ "CanonicalEndpoint": r.canonicalEndpoint,
+ "StaticStorage": r.supplements.StaticStorage,
+ "PhotoStorage": r.supplements.PhotoStorage,
}
statusCode, err := currentRoute.Render(c, r.supplements, lang, defaultMap)
@@ -467,13 +455,12 @@ func (r *Router) generalPage(c *fiber.Ctx, route Route, lang string) error {
}
valueMap := fiber.Map{
- "L": r.supplements.Localization[lang],
- "Lang": lang,
- "Path": trimmedPath,
- "QueryString": queryString,
- "CanonicalEndpoint": r.canonicalEndpoint,
- "StaticStorageBaseUrl": r.supplements.StaticStorage.BaseUrl,
- "ThumbnailBaseUrl": r.supplements.PhotoStorage.Thumbnail320p.BaseUrl,
+ "Lang": lang,
+ "Path": trimmedPath,
+ "QueryString": queryString,
+ "CanonicalEndpoint": r.canonicalEndpoint,
+ "StaticStorage": r.supplements.StaticStorage,
+ "PhotoStorage": r.supplements.PhotoStorage,
}
var err error
@@ -576,11 +563,12 @@ func (r *Router) generalPageSegment(c *fiber.Ctx, part string) error {
cacheKey := ""
trimmedPath := strings.Trim(path, "/")
+ requestQuery := string(c.Request().URI().QueryString())
switch route.ToCache() {
case ByUrlOnly:
cacheKey = fmt.Sprintf("%s.%s.%s", method, part, trimmedPath)
case ByUrlAndQuery:
- cacheKey = fmt.Sprintf("%s.%s.%s.%s", method, part, trimmedPath, queryString)
+ cacheKey = fmt.Sprintf("%s.%s.%s.%s.%s", method, part, trimmedPath, queryString, requestQuery)
}
if route.ToCache() != Disabled {
@@ -592,13 +580,12 @@ func (r *Router) generalPageSegment(c *fiber.Ctx, part string) error {
var statusCode int
defaultMap := fiber.Map{
- "L": r.supplements.Localization[lang],
- "Lang": lang,
- "Path": strings.Trim(path, "/"),
- "QueryString": queryString,
- "CanonicalEndpoint": r.canonicalEndpoint,
- "StaticStorageBaseUrl": r.supplements.StaticStorage.BaseUrl,
- "ThumbnailBaseUrl": r.supplements.PhotoStorage.Thumbnail320p.BaseUrl,
+ "Lang": lang,
+ "Path": strings.Trim(path, "/"),
+ "QueryString": queryString,
+ "CanonicalEndpoint": r.canonicalEndpoint,
+ "StaticStorage": r.supplements.StaticStorage,
+ "PhotoStorage": r.supplements.PhotoStorage,
}
switch part {