Diffstat (limited to 'internal/router/router.go')
| -rw-r--r-- | internal/router/router.go | 152 |
1 files changed, 107 insertions, 45 deletions
diff --git a/internal/router/router.go b/internal/router/router.go index 5230e74..a11a934 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -7,8 +7,11 @@ import ( "fmt" "html/template" "log/slog" + "net" "net/url" + "os" "slices" + "strconv" "strings" "time" @@ -20,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" @@ -92,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] @@ -101,9 +102,9 @@ type Supplements struct { BlogTrigger *blogtrigger.BlogTriggerScheduler TemplateManager *templatemanager.TemplateManager MarkdownRenderer goldmark.Markdown - Meta config.MetaConfig + Meta []config.MetaConfig PhotoStorage config.PhotoStorageConfig - StaticStorage config.PhotoTypeConfig + StaticStorage config.StaticStorageConfig } type Router struct { @@ -112,6 +113,7 @@ type Router struct { templatedRoutes map[string]map[string]Route templatedPathMatcher *PathMatcher canonicalEndpoint string + endpoint config.EndpointConfig } func NewRouter(cfg *config.Config) (*Router, error) { @@ -145,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( @@ -196,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) } @@ -256,7 +249,7 @@ func NewRouter(cfg *config.Config) (*Router, error) { templatedRoutes := make(map[string]map[string]Route) templatedPathMatcher := NewPathMatcher() - return &Router{supplements, app, templatedRoutes, templatedPathMatcher, cfg.CanonicalEndpoint}, nil + return &Router{supplements, app, templatedRoutes, templatedPathMatcher, cfg.CanonicalEndpoint, cfg.Endpoint}, nil } func (r *Router) InitRoutes() (err error) { @@ -305,16 +298,17 @@ 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{ - "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) @@ -367,6 +361,7 @@ 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 }) @@ -382,10 +377,35 @@ func (r *Router) InitRoutes() (err error) { return nil } -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) +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)") } + return nil } @@ -409,6 +429,7 @@ func (r *Router) Close() (err error) { } slog.Debug("closing page cache") r.supplements.PageCache.Close() + return errors.Join(allErrors...) } @@ -418,8 +439,8 @@ func (r *Router) generalPage(c *fiber.Ctx, route Route, lang string) error { path := c.Path() method := c.Method() trimmedPath := strings.Trim(path, "/") - cacheKey := "" queryString := c.Request().URI().QueryString() + cacheKey := "" switch route.ToCache() { case ByUrlOnly: @@ -434,12 +455,12 @@ func (r *Router) generalPage(c *fiber.Ctx, route Route, lang string) error { } valueMap := fiber.Map{ - "L": r.supplements.Localization[lang], - "Lang": lang, - "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 @@ -456,10 +477,52 @@ func (r *Router) generalPage(c *fiber.Ctx, route Route, lang string) error { valueMap["LinkedData"] = template.JS(ldBytes) } + syntheticReferer := path + if len(queryString) > 0 { + syntheticReferer += "?" + string(queryString) + } + 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) 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") + 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") } go r.supplements.PageCache.SetWithTTL(cacheKey, content, int64(len(content)), route.CacheDuration()) @@ -500,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 { @@ -516,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 { @@ -614,8 +677,7 @@ func (r *Router) getAndValidateLang(c *fiber.Ctx, langSetting LangSetting, defau return lang, nil } } - 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)) + return "", fiber.NewError(fiber.StatusBadRequest, 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) { |