package server import ( "context" "database/sql" "html/template" "log/slog" "sync" "time" "github.com/writekitapp/writekit/internal/build/templates" "github.com/writekitapp/writekit/internal/markdown" "github.com/writekitapp/writekit/internal/tenant" ) type renderedPage struct { path string html []byte etag string } func (s *Server) rebuildSite(ctx context.Context, tenantID string, db *sql.DB, host string) { q := tenant.NewQueries(db) settings, err := q.GetSettings(ctx) if err != nil { slog.Error("rebuildSite: get settings", "error", err, "tenantID", tenantID) return } posts, err := q.ListPosts(ctx, false) if err != nil { slog.Error("rebuildSite: list posts", "error", err, "tenantID", tenantID) return } baseURL := getBaseURL(host) siteName := getSettingOr(settings, "site_name", "My Blog") siteDesc := getSettingOr(settings, "site_description", "") codeTheme := getSettingOr(settings, "code_theme", "github") fontKey := getSettingOr(settings, "font", "system") isDemo := getSettingOr(settings, "is_demo", "") == "true" pageData := templates.PageData{ SiteName: siteName, Year: time.Now().Year(), FontURL: templates.GetFontURL(fontKey), FontFamily: templates.GetFontFamily(fontKey), Settings: settingsToMap(settings), NoIndex: isDemo, } var pages []renderedPage var mu sync.Mutex var wg sync.WaitGroup addPage := func(path string, html []byte) { mu.Lock() pages = append(pages, renderedPage{path, html, computeETag(html)}) mu.Unlock() } postSummaries := make([]templates.PostSummary, len(posts)) for i, p := range posts { postSummaries[i] = templates.PostSummary{ Slug: p.Slug, Title: p.Title, Description: p.Description, Date: timeOrZero(p.PublishedAt), } } wg.Add(1) go func() { defer wg.Done() homePosts := postSummaries if len(homePosts) > 10 { homePosts = homePosts[:10] } data := templates.HomeData{ PageData: pageData, Posts: homePosts, HasMore: len(postSummaries) > 10, } data.Title = siteName data.Description = siteDesc data.CanonicalURL = baseURL + "/" data.OGType = "website" html, err := templates.RenderHome(data) if err != nil { slog.Error("rebuildSite: render home", "error", err) return } addPage("/", html) }() wg.Add(1) go func() { defer wg.Done() data := templates.BlogData{ PageData: pageData, Posts: postSummaries, } data.Title = "Posts - " + siteName data.Description = "All posts" data.CanonicalURL = baseURL + "/posts" data.OGType = "website" html, err := templates.RenderBlog(data) if err != nil { slog.Error("rebuildSite: render blog", "error", err) return } addPage("/posts", html) }() for _, p := range posts { wg.Add(1) go func(post tenant.Post) { defer wg.Done() contentHTML := post.ContentHTML if contentHTML == "" && post.ContentMD != "" { contentHTML, _ = markdown.RenderWithTheme(post.ContentMD, codeTheme) } interactionConfig := q.GetInteractionConfig(ctx) structuredData := buildArticleSchema(&post, siteName, baseURL) data := templates.PostData{ PageData: pageData, Post: templates.PostDetail{ Slug: post.Slug, Title: post.Title, Description: post.Description, Date: timeOrZero(post.PublishedAt), Tags: post.Tags, }, ContentHTML: template.HTML(contentHTML), InteractionConfig: interactionConfig, } data.Title = post.Title + " - " + siteName data.Description = post.Description data.CanonicalURL = baseURL + "/posts/" + post.Slug data.OGType = "article" data.StructuredData = template.JS(structuredData) html, err := templates.RenderPost(data) if err != nil { slog.Error("rebuildSite: render post", "error", err, "slug", post.Slug) return } addPage("/posts/"+post.Slug, html) }(p) } wg.Wait() for _, p := range pages { if err := q.SetPage(ctx, p.path, p.html, p.etag); err != nil { slog.Error("rebuildSite: save page", "error", err, "path", p.path) } } slog.Info("rebuildSite: complete", "tenantID", tenantID, "pages", len(pages)) }