This commit is contained in:
Josh 2026-01-09 00:16:46 +02:00
commit d69342b2e9
160 changed files with 28681 additions and 0 deletions

176
internal/server/build.go Normal file
View file

@ -0,0 +1,176 @@
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)
}
}
if s.cloudflare.IsConfigured() {
urls := make([]string, len(pages))
for i, p := range pages {
urls[i] = baseURL + p.path
}
if err := s.cloudflare.PurgeURLs(ctx, urls); err != nil {
slog.Error("rebuildSite: purge cache", "error", err)
}
}
slog.Info("rebuildSite: complete", "tenantID", tenantID, "pages", len(pages))
}