Diffstat (limited to 'main.go')
| -rw-r--r-- | main.go | 205 |
1 files changed, 82 insertions, 123 deletions
@@ -1,59 +1,43 @@ package main import ( - "database/sql" - "errors" + "bytes" "flag" + "fmt" + "html/template" + "log" "log/slog" "os" - "os/signal" - "syscall" + "strconv" + "strings" "github.com/SayaAndy/saya-today-web/config" "github.com/SayaAndy/saya-today-web/internal/b2" - "github.com/SayaAndy/saya-today-web/internal/factgiver" - "github.com/SayaAndy/saya-today-web/internal/glightbox" - "github.com/SayaAndy/saya-today-web/internal/router" + "github.com/SayaAndy/saya-today-web/internal/frontmatter" + "github.com/SayaAndy/saya-today-web/internal/lightgallery" "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/cors" - "github.com/golang-migrate/migrate/v4" - "github.com/golang-migrate/migrate/v4/database/sqlite3" + "github.com/gofiber/template/html/v2" "github.com/yuin/goldmark" "github.com/yuin/goldmark/parser" - "github.com/yuin/goldmark/renderer" - "github.com/yuin/goldmark/renderer/html" - "github.com/yuin/goldmark/util" - - _ "github.com/golang-migrate/migrate/v4/source/file" - _ "github.com/mattn/go-sqlite3" + gmhtml "github.com/yuin/goldmark/renderer/html" ) var ( md = goldmark.New( goldmark.WithExtensions( - glightbox.NewGLightboxExtension(), + lightgallery.NewLightGalleryExtension(), tailwind.NewTailwindExtension(), ), goldmark.WithParserOptions( parser.WithAutoHeadingID(), - parser.WithAttribute(), ), - goldmark.WithRenderer( - renderer.NewRenderer( - renderer.WithNodeRenderers( - util.Prioritized(tailwind.NewCustomLinkRenderer(html.WithUnsafe(), html.WithXHTML()), 50), - util.Prioritized(html.NewRenderer(html.WithXHTML()), 100), - ), - ), + goldmark.WithRendererOptions( + gmhtml.WithXHTML(), ), ) - b2Client *b2.B2Client - configPath = flag.String("c", "config.yaml", "Path to the configuration file (in YAML format)") - availableLanguages = make([]string, 0) - localization = make(map[string]*locale.LocaleConfig) + b2Client *b2.B2Client + configPath = flag.String("c", "config.yaml", "Path to the configuration file (in YAML format)") ) func main() { @@ -70,119 +54,94 @@ func main() { slog.SetLogLoggerLevel(cfg.LogLevel) slog.Info("starting sayana-web server...") - db, err := sql.Open(cfg.Auth.Db.Type, cfg.Auth.Db.Cfg.DSN) - if err != nil { - slog.Error("fail to initialize db", slog.String("error", err.Error())) - os.Exit(1) - } - - driver, err := sqlite3.WithInstance(db, &sqlite3.Config{}) - if err != nil { - slog.Error("fail to initialize driver for migrating db", slog.String("error", err.Error())) - os.Exit(1) - } - - m, err := migrate.NewWithDatabaseInstance( - "file://migrations", - cfg.Auth.Db.Type, driver) - if err != nil { - slog.Error("fail to initialize migration client", slog.String("error", err.Error())) - os.Exit(1) - } - - if err = m.Up(); err != nil && err == errors.New("no change") { - slog.Error("fail to apply migrations", slog.String("error", err.Error())) - os.Exit(1) - } - slog.Info("successfully applied migrations") - b2Client, err = b2.NewB2Client(&cfg.BlogPages.Storage.Config) if err != nil { slog.Error("fail to initialize b2 client", slog.String("error", err.Error())) os.Exit(1) } - for _, lang := range cfg.AvailableLanguages { - localeCfg, err := locale.InitConfig(cfg.LocalePath + lang.LocFile) - if err != nil { - slog.Warn("fail to initialize a locale", slog.String("locale", lang.Name), slog.String("error", err.Error())) - continue - } - availableLanguages = append(availableLanguages, lang.Name) - localization[lang.Name] = localeCfg - } + engine := html.New("./views", ".html") app := fiber.New(fiber.Config{ - ProxyHeader: "X-Forwarded-For", + Views: engine, }) - app.Use(cors.New(cors.Config{ - AllowOrigins: "https://f003.backblazeb2.com", - AllowMethods: "GET,POST,OPTIONS", - AllowHeaders: "Origin, Content-Type, Accept", - })) + app.Get("/", func(c *fiber.Ctx) error { + pages, err := b2Client.Scan() + status := fiber.StatusOK + if err != nil { + status = fiber.StatusPartialContent + pages = []*b2.BlogPage{} + } - router.CCache, err = router.NewClientCache(db, []byte(cfg.Auth.Salt)) - if err != nil { - slog.Error("fail to initialize client cache", slog.String("error", err.Error())) - os.Exit(1) - } + pageMeta := make([]map[string]string, 0, len(pages)) + for _, page := range pages { + slog.Debug("enlist page for catalogue", slog.Any("page", page), slog.String("endpoint", "/")) + pageMeta = append(pageMeta, map[string]string{ + "Link": page.Link, + "Title": page.Metadata.Title, + "PublishedTime": page.Metadata.PublishedTime.Format("2006-01-02 15:04:05 -0700"), + "ActionDate": page.Metadata.ActionDate, + "ShortDescription": page.Metadata.ShortDescription, + "Thumbnail": page.Metadata.Thumbnail, + "Tags": strings.Join(page.Metadata.Tags, ", "), + }) + } - 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. + return c.Status(status).Render("index", fiber.Map{ + "PublishedYear": "2025", + "BlogPages": pageMeta, + }) }) - if err != nil { - slog.Error("fail to initialize page cache", slog.String("error", err.Error())) - os.Exit(1) - } - router.FactGiver, err = factgiver.NewFactGiver(&cfg.FactGiver, availableLanguages) - if err != nil { - slog.Error("fail to initialize fact giver", slog.String("error", err.Error())) - os.Exit(1) - } + app.Get("/blog/:lang/:title", func(c *fiber.Ctx) error { + metadata, parsedMarkdownDesktop, parsedMarkdownMobile, err := readBlogPost(c.Params("lang") + "/" + c.Params("title")) + if err != nil { + c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) + return c.Status(fiber.ErrNotFound.Code).SendString(fmt.Sprintf("failed to find '%s' post", c.Params("title"))) + } - app.Get("/", router.Root(cfg.AvailableLanguages)) - app.Get("/:lang<len(2)>", router.Api_V1_GeneralPage(localization, availableLanguages)) - app.Get("/:lang/map", router.Lang_Map(localization, availableLanguages, b2Client)) - app.Get("/:lang/blog", router.Api_V1_GeneralPage(localization, availableLanguages)) - app.Get("/:lang/blog/:title", router.Api_V1_GeneralPage(localization, availableLanguages)) + geolocationParts := strings.Split(metadata.Geolocation, " ") + var x, y, areaError string + if len(geolocationParts) >= 2 { + x = geolocationParts[0] + y = geolocationParts[1] + } + if len(geolocationParts) >= 3 { + areaError = geolocationParts[2] + } - 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.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)) + return c.Render("layouts/blog-page", fiber.Map{ + "Title": metadata.Title, + "PublishedDate": metadata.PublishedTime.Format("2006-01-02 15:04:05 -07:00"), + "PublishedYear": strconv.Itoa(metadata.PublishedTime.Year()), + "ParsedMarkdownDesktop": template.HTML(parsedMarkdownDesktop), + "ParsedMarkdownMobile": template.HTML(parsedMarkdownMobile), + "MapLocationX": x, + "MapLocationY": y, + "MapLocationAreaMeters": areaError, + }) + }) app.Static("/", "./static") - go func() { - if err := app.Listen(":3000"); err != nil { - slog.Error("error while running fiber server", slog.String("error", err.Error())) - panic(err) - } - }() - - sigChan := make(chan os.Signal, 1) - signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) + log.Fatal(app.Listen(":3000")) +} - <-sigChan - slog.Info("gracefully shutting down...") - if err = app.Shutdown(); err != nil { - slog.Error("fail to shutdown fiber server", slog.String("error", err.Error())) +func readBlogPost(sourceName string) (metadata *frontmatter.Metadata, desktopBody string, mobileBody string, err error) { + metadata, markdown, err := b2Client.ReadFrontmatter(sourceName + ".md") + if err != nil { + return nil, "", "", fmt.Errorf("failed to read a frontmatter file: %w", err) } - if err = router.CCache.Close(); err != nil { - slog.Error("fail to dump cache", slog.String("error", err.Error())) + + var bufDesktop bytes.Buffer + if err := md.Convert(markdown, &bufDesktop); err != nil { + return nil, "", "", fmt.Errorf("convert source context from md to html (desktop version): %w", err) } - if err = db.Close(); err != nil { - slog.Error("fail to close db connection", slog.String("error", err.Error())) + var bufMobile bytes.Buffer + if err := md.Convert(markdown, &bufMobile); err != nil { + return nil, "", "", fmt.Errorf("convert source context from md to html (mobile version): %w", err) } - router.PCache.Close() - db.Close() + + return metadata, bufDesktop.String(), bufMobile.String(), nil } |