summaryrefslogtreecommitdiffci
path: root/internal/router/router.go
diff refs
from: back
to: back
| flip
diff options
context:
space:
mode:
Diffstat (limited to 'internal/router/router.go')
-rw-r--r--internal/router/router.go243
1 files changed, 54 insertions, 189 deletions
diff --git a/internal/router/router.go b/internal/router/router.go
index a11a934..c0556f0 100644
--- a/internal/router/router.go
+++ b/internal/router/router.go
@@ -2,32 +2,26 @@ package router
import (
"database/sql"
- "encoding/json"
"errors"
"fmt"
- "html/template"
"log/slog"
- "net"
"net/url"
- "os"
"slices"
- "strconv"
"strings"
"time"
"github.com/SayaAndy/saya-today-web/config"
- "github.com/SayaAndy/saya-today-web/internal/blog"
+ "github.com/SayaAndy/saya-today-web/internal/b2"
"github.com/SayaAndy/saya-today-web/internal/blogtrigger"
"github.com/SayaAndy/saya-today-web/internal/factgiver"
"github.com/SayaAndy/saya-today-web/internal/glightbox"
"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"
"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"
@@ -58,19 +52,6 @@ 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
@@ -78,12 +59,7 @@ type Route interface {
CacheDuration() time.Duration
ToValidateLang() LangSetting
TemplatesToInject() []string
- SitemapInfo(supplements *Supplements) []SitemapInfo
- ContentType() string
- RateLimiter() *fiber.Handler
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 []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)
@@ -93,7 +69,8 @@ type Route interface {
type Supplements struct {
DB *sql.DB
- BlogClient blog.Client
+ B2Client *b2.B2Client
+ Localization map[string]*locale.LocaleConfig
AvailableLanguages []config.AvailableLanguageConfig
ClientCache *ClientCache
PageCache *ristretto.Cache[string, []byte]
@@ -102,9 +79,6 @@ type Supplements struct {
BlogTrigger *blogtrigger.BlogTriggerScheduler
TemplateManager *templatemanager.TemplateManager
MarkdownRenderer goldmark.Markdown
- Meta []config.MetaConfig
- PhotoStorage config.PhotoStorageConfig
- StaticStorage config.StaticStorageConfig
}
type Router struct {
@@ -112,8 +86,6 @@ type Router struct {
app *fiber.App
templatedRoutes map[string]map[string]Route
templatedPathMatcher *PathMatcher
- canonicalEndpoint string
- endpoint config.EndpointConfig
}
func NewRouter(cfg *config.Config) (*Router, error) {
@@ -145,14 +117,23 @@ func NewRouter(cfg *config.Config) (*Router, error) {
}
slog.Debug("successfully applied migrations")
- supplements.BlogClient, err = blog.NewClientMap[cfg.BlogPages.Storage.Type](&cfg.BlogPages.Storage)
+ supplements.B2Client, err = b2.NewB2Client(&cfg.BlogPages.Storage.Config)
if err != nil {
- return nil, fmt.Errorf("fail to initialize blog client: type %s: %w", cfg.BlogPages.Storage.Type, err)
+ 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
}
supplements.MarkdownRenderer = goldmark.New(
goldmark.WithExtensions(
- glightbox.NewGLightboxExtension(cfg.PhotoStorage),
+ glightbox.NewGLightboxExtension(),
tailwind.NewTailwindExtension(),
),
goldmark.WithParserOptions(
@@ -189,13 +170,13 @@ 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))
+ cfg.Mail.PublicName, cfg.Mail.MailAddress, cfg.Mail.Username, cfg.Mail.Password, []byte(cfg.Mail.Salt), supplements.Localization)
if err != nil {
return nil, fmt.Errorf("fail to initialize mailer: %w", err)
}
- supplements.BlogTrigger, err = blogtrigger.NewBlogTriggerScheduler(supplements.BlogClient, cfg.AvailableLanguages, cfg.Mail.Trigger.OnNewPost,
- func(bp []*blog.Page) error {
+ supplements.BlogTrigger, err = blogtrigger.NewBlogTriggerScheduler(supplements.B2Client, cfg.AvailableLanguages, cfg.Mail.Trigger.OnNewPost,
+ func(bp []*b2.BlogPage) error {
for _, post := range bp {
if err := supplements.Mailer.NewPost(post); err != nil {
return err
@@ -212,10 +193,6 @@ func NewRouter(cfg *config.Config) (*Router, error) {
return nil, fmt.Errorf("fail to initialize template manager: %w", err)
}
- supplements.Meta = cfg.Meta
- supplements.PhotoStorage = cfg.PhotoStorage
- supplements.StaticStorage = cfg.StaticStorage
-
enablePrintRoutes := false
if cfg.LogLevel <= slog.LevelDebug {
enablePrintRoutes = true
@@ -227,29 +204,15 @@ func NewRouter(cfg *config.Config) (*Router, error) {
})
app.Use(cors.New(cors.Config{
- AllowOrigins: strings.Join(cfg.AllowOrigins, ","),
+ AllowOrigins: "https://f003.backblazeb2.com",
AllowMethods: "GET,POST,OPTIONS",
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, cfg.CanonicalEndpoint, cfg.Endpoint}, nil
+ return &Router{supplements, app, templatedRoutes, templatedPathMatcher}, nil
}
func (r *Router) InitRoutes() (err error) {
@@ -261,10 +224,6 @@ func (r *Router) InitRoutes() (err error) {
return fmt.Errorf("failed to add '%s %s' route into template manager: %w", method, match, err)
}
- if rateLimiter := route.RateLimiter(); rateLimiter != nil {
- r.app.Use(match, *rateLimiter)
- }
-
if route.IsTemplated() {
if _, ok := r.templatedRoutes[method]; !ok {
r.templatedRoutes[method] = make(map[string]Route)
@@ -298,17 +257,13 @@ func (r *Router) InitRoutes() (err error) {
cacheKey = fmt.Sprintf("%s.full-page.%s", method, trimmedPath)
case ByUrlAndQuery:
cacheKey = fmt.Sprintf("%s.full-page.%s.%s", method, trimmedPath, queryString)
- case Disabled:
- c.Set("Cache-Control", "no-store, no-cache, must-revalidate")
}
defaultMap := fiber.Map{
- "Lang": lang,
- "Path": trimmedPath,
- "QueryString": queryString,
- "CanonicalEndpoint": r.canonicalEndpoint,
- "StaticStorage": r.supplements.StaticStorage,
- "PhotoStorage": r.supplements.PhotoStorage,
+ "L": r.supplements.Localization[lang],
+ "Lang": lang,
+ "Path": trimmedPath,
+ "QueryString": queryString,
}
statusCode, err := currentRoute.Render(c, r.supplements, lang, defaultMap)
@@ -347,8 +302,8 @@ func (r *Router) InitRoutes() (err error) {
go r.supplements.PageCache.SetWithTTL(cacheKey, content, int64(len(content)), route.CacheDuration())
}
- c.Set(fiber.HeaderContentType, route.ContentType())
- return c.Status(statusCode).Send(content)
+ c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8)
+ return c.Status(statusCode).Type("html").Send(content)
})
}
}
@@ -361,7 +316,6 @@ func (r *Router) InitRoutes() (err error) {
c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8)
return c.Status(fiber.ErrBadRequest.Code).SendString(fmt.Sprintf("unknown segment '%s'", part))
}
- c.Set("Cache-Control", "no-store, no-cache, must-revalidate")
err := r.generalPageSegment(c, part)
return err
})
@@ -372,40 +326,15 @@ func (r *Router) InitRoutes() (err error) {
r.app.Static("/", "./static")
- // Routes = make([]Route, 0)
+ Routes = make([]Route, 0)
return nil
}
-func (r *Router) Listen() error {
- switch r.endpoint.Type {
- case "unix":
- unixConfig := r.endpoint.Config.(*config.UnixConfig)
- endpoint, _ := strings.CutPrefix(unixConfig.Path, "unix://")
-
- if err := os.Remove(endpoint); err != nil && !errors.Is(err, os.ErrNotExist) {
- return fmt.Errorf("error while cleaning up existing unix socket: %w", err)
- }
-
- ln, err := net.Listen("unix", endpoint)
- if err != nil {
- return fmt.Errorf("error while initializing unix listener: %w", err)
- }
- chmod, _ := strconv.ParseUint(unixConfig.Chmod[1:], 8, 32)
- os.Chmod(unixConfig.Path, os.FileMode(chmod))
- if err := r.app.Listener(ln); err != nil {
- return fmt.Errorf("error while running fiber server: %w", err)
- }
- case "http":
- httpConfig := r.endpoint.Config.(*config.HttpConfig)
- fmt.Print(httpConfig)
- if err := r.app.Listen(httpConfig.ListenOn); err != nil {
- return fmt.Errorf("error while running fiber server: %w", err)
- }
- default:
- return fmt.Errorf("error with initializing fiber server: invalid endpoint type (supported are unix and http)")
+func (r *Router) Listen(endpoint string) error {
+ if err := r.app.Listen(endpoint); err != nil {
+ return fmt.Errorf("error while running fiber server: %w", err)
}
-
return nil
}
@@ -429,7 +358,6 @@ func (r *Router) Close() (err error) {
}
slog.Debug("closing page cache")
r.supplements.PageCache.Close()
-
return errors.Join(allErrors...)
}
@@ -439,8 +367,8 @@ func (r *Router) generalPage(c *fiber.Ctx, route Route, lang string) error {
path := c.Path()
method := c.Method()
trimmedPath := strings.Trim(path, "/")
- queryString := c.Request().URI().QueryString()
cacheKey := ""
+ queryString := c.Request().URI().QueryString()
switch route.ToCache() {
case ByUrlOnly:
@@ -450,84 +378,23 @@ 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, route.ContentType())
- return c.Status(fiber.StatusOK).Send(val)
- }
-
- valueMap := fiber.Map{
- "Lang": lang,
- "Path": trimmedPath,
- "QueryString": queryString,
- "CanonicalEndpoint": r.canonicalEndpoint,
- "StaticStorage": r.supplements.StaticStorage,
- "PhotoStorage": r.supplements.PhotoStorage,
- }
-
- var err error
- if valueMap["Meta"], err = route.AddMeta(c, r.supplements, lang, valueMap); err != nil {
- 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)
- }
-
- syntheticReferer := path
- if len(queryString) > 0 {
- syntheticReferer += "?" + string(queryString)
+ c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8)
+ return c.Status(fiber.StatusOK).Type("html").Send(val)
}
- c.Request().Header.Set("Referer", syntheticReferer)
- parts := []struct {
- name string
- key string
- render func(c *fiber.Ctx, supplements *Supplements, lang string, templateMap fiber.Map) (int, error)
- }{
- {"top-embeds", "RenderedTopEmbeds", route.RenderTopEmbeds},
- {"header", "RenderedHeader", route.RenderHeader},
- {"body", "RenderedBody", route.RenderBody},
- {"footer", "RenderedFooter", route.RenderFooter},
- {"bottom-embeds", "RenderedBottomEmbeds", route.RenderBottomEmbeds},
- }
-
- for _, p := range parts {
- statusCode, err := p.render(c, r.supplements, lang, valueMap)
- if err != nil {
- slog.Error("failed to render segment for full page",
- slog.String("path", path),
- slog.String("segment", p.name),
- slog.String("error", err.Error()),
- )
- c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8)
- return c.Status(statusCode).SendString(err.Error())
- }
- segContent, err := r.supplements.TemplateManager.Render("general-page-"+p.name, valueMap, route.TemplatesToInject()...)
- if err != nil {
- slog.Error("failed to render segment template for full page",
- slog.String("path", path),
- slog.String("segment", p.name),
- slog.String("error", err.Error()),
- )
- c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8)
- return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate segment")
- }
- valueMap[p.key] = template.HTML(segContent)
- }
-
- content, err := r.supplements.TemplateManager.Render("general-page", valueMap)
+ content, err := r.supplements.TemplateManager.Render("general-page", fiber.Map{
+ "L": r.supplements.Localization[lang],
+ "Lang": lang,
+ "QueryString": queryString,
+ })
if err != nil {
- slog.Warn("failed to generate full page", slog.String("path", path), slog.String("error", err.Error()))
- return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate full page")
+ 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 r.supplements.PageCache.SetWithTTL(cacheKey, content, int64(len(content)), route.CacheDuration())
- c.Set(fiber.HeaderContentType, route.ContentType())
- return c.Status(fiber.StatusOK).Send(content)
+ c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8)
+ return c.Status(fiber.StatusOK).Type("html").Send(content)
}
func (r *Router) generalPageSegment(c *fiber.Ctx, part string) error {
@@ -563,29 +430,26 @@ 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.%s", method, part, trimmedPath, queryString, requestQuery)
+ cacheKey = fmt.Sprintf("%s.%s.%s.%s", method, part, trimmedPath, queryString)
}
if route.ToCache() != Disabled {
if val, ok := r.supplements.PageCache.Get(cacheKey); val != nil && ok {
- c.Set(fiber.HeaderContentType, route.ContentType())
- return c.Status(fiber.StatusOK).Send(val)
+ c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8)
+ return c.Status(fiber.StatusOK).Type("html").Send(val)
}
}
var statusCode int
defaultMap := fiber.Map{
- "Lang": lang,
- "Path": strings.Trim(path, "/"),
- "QueryString": queryString,
- "CanonicalEndpoint": r.canonicalEndpoint,
- "StaticStorage": r.supplements.StaticStorage,
- "PhotoStorage": r.supplements.PhotoStorage,
+ "L": r.supplements.Localization[lang],
+ "Lang": lang,
+ "Path": strings.Trim(path, "/"),
+ "QueryString": queryString,
}
switch part {
@@ -633,8 +497,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, route.ContentType())
- return c.Status(statusCode).Send(content)
+ c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8)
+ return c.Status(statusCode).Type("html").Send(content)
}
func (r *Router) getAndValidateLang(c *fiber.Ctx, langSetting LangSetting, defaultLang ...string) (string, error) {
@@ -677,7 +541,8 @@ func (r *Router) getAndValidateLang(c *fiber.Ctx, langSetting LangSetting, defau
return lang, nil
}
}
- return "", fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("lang value is invalid: '%s' is not considered an available language", lang))
+ c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8)
+ return "", c.Status(fiber.ErrBadRequest.Code).SendString(fmt.Sprintf("lang value is invalid: '%s' is not considered an available language", lang))
}
func GetPathFromReferer(c *fiber.Ctx) (path string, pathParts []string, queryString string, err error) {