Added site setting.

This commit is contained in:
Leon Mika 2025-02-02 09:54:30 +11:00
parent 39611070f8
commit 3774c903e2
17 changed files with 170 additions and 18 deletions

View file

@ -76,3 +76,28 @@ func (q *Queries) NewSite(ctx context.Context, arg NewSiteParams) (int64, error)
err := row.Scan(&id) err := row.Scan(&id)
return id, err return id, err
} }
const updateSite = `-- name: UpdateSite :exec
UPDATE sites SET
name = $2,
title = $3,
theme = $4
WHERE id = $1
`
type UpdateSiteParams struct {
ID int64
Name string
Title string
Theme string
}
func (q *Queries) UpdateSite(ctx context.Context, arg UpdateSiteParams) error {
_, err := q.db.Exec(ctx, updateSite,
arg.ID,
arg.Name,
arg.Title,
arg.Theme,
)
return err
}

2
go.mod
View file

@ -48,5 +48,5 @@ require (
golang.org/x/sys v0.29.0 // indirect golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.21.0 // indirect golang.org/x/text v0.21.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
lmika.dev/pkg/modash v0.0.0-20250127022145-5dcbffe270a1 // indirect lmika.dev/pkg/modash v0.0.0-20250201221851-97d4b9b4a1ac // indirect
) )

2
go.sum
View file

@ -97,3 +97,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
lmika.dev/pkg/modash v0.0.0-20250127022145-5dcbffe270a1 h1:Seqp9vlIw3uJBL0V/eWIM3dAnSuToJ/cztkRQtl3g20= lmika.dev/pkg/modash v0.0.0-20250127022145-5dcbffe270a1 h1:Seqp9vlIw3uJBL0V/eWIM3dAnSuToJ/cztkRQtl3g20=
lmika.dev/pkg/modash v0.0.0-20250127022145-5dcbffe270a1/go.mod h1:8NDl/yR1eCCEhip9FJlVuMNXIeaztQ0Ks/tizExFcTI= lmika.dev/pkg/modash v0.0.0-20250127022145-5dcbffe270a1/go.mod h1:8NDl/yR1eCCEhip9FJlVuMNXIeaztQ0Ks/tizExFcTI=
lmika.dev/pkg/modash v0.0.0-20250201221851-97d4b9b4a1ac h1:i/C+DYDCVQTQHtv7w1O8m20RMez6YS9fUIlhAGjTZhU=
lmika.dev/pkg/modash v0.0.0-20250201221851-97d4b9b4a1ac/go.mod h1:8NDl/yR1eCCEhip9FJlVuMNXIeaztQ0Ks/tizExFcTI=

View file

@ -46,6 +46,27 @@ func (s *Site) Show(c *fiber.Ctx) error {
}, "layouts/main") }, "layouts/main")
} }
func (s *Site) Settings(c *fiber.Ctx) error {
return c.Render("sites/settings", fiber.Map{
"themes": s.Site.Themes(),
}, "layouts/site")
}
func (s *Site) SaveSettings(c *fiber.Ctx) error {
site := GetSite(c)
var req sites.NewSettings
if err := c.BodyParser(&req); err != nil {
return err
}
if err := s.Site.SaveSettings(c.Context(), site, req); err != nil {
return err
}
return c.Redirect(fmt.Sprintf("/sites/%v/settings", site.ID))
}
func (s *Site) Rebuild(c *fiber.Ctx) error { func (s *Site) Rebuild(c *fiber.Ctx) error {
if err := s.Site.Rebuild(c.UserContext(), GetSite(c)); err != nil { if err := s.Site.Rebuild(c.UserContext(), GetSite(c)); err != nil {
return err return err

View file

@ -129,6 +129,9 @@ func main() {
r.Get("/posts/:postId", postHandlers.Edit) r.Get("/posts/:postId", postHandlers.Edit)
r.Post("/posts/:postId", postHandlers.Update) r.Post("/posts/:postId", postHandlers.Update)
r.Delete("/posts/:postId", postHandlers.Delete) r.Delete("/posts/:postId", postHandlers.Delete)
r.Get("/settings", siteHandlers.Settings)
r.Post("/settings", siteHandlers.SaveSettings)
}) })
jobService.Start() jobService.Start()

View file

@ -1,6 +1,7 @@
package models package models
type ThemeMeta struct { type ThemeMeta struct {
ID string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
URL string `json:"repo"` URL string `json:"repo"`

View file

@ -35,3 +35,12 @@ func (db *DB) GetSite(ctx context.Context, id int64) (models.Site, error) {
Theme: site.Theme, Theme: site.Theme,
}, nil }, nil
} }
func (db *DB) UpdateSite(ctx context.Context, site models.Site) error {
return db.q.UpdateSite(ctx, dbq.UpdateSiteParams{
ID: site.ID,
Name: site.Name,
Title: site.Title,
Theme: site.Theme,
})
}

View file

@ -80,7 +80,7 @@ func (p *Provider) PublishSite(ctx context.Context, site models.Site, target mod
func (p *Provider) ReconfigureSite(ctx context.Context, site models.Site) error { func (p *Provider) ReconfigureSite(ctx context.Context, site models.Site) error {
hugoCfg := hugoConfig{ hugoCfg := hugoConfig{
Title: site.Name, Title: site.Title,
LanguageCode: "en", LanguageCode: "en",
Theme: site.Theme, Theme: site.Theme,
Markup: hugoConfigMarkup{ Markup: hugoConfigMarkup{

View file

@ -2,11 +2,26 @@ package themes
import "lmika.dev/lmika/hugo-cms/models" import "lmika.dev/lmika/hugo-cms/models"
var themes = map[string]models.ThemeMeta{ var themes = []models.ThemeMeta{
"bear": models.ThemeMeta{ {
Name: "bear", ID: "bear",
Name: "Bear",
URL: "https://github.com/janraasch/hugo-bearblog", URL: "https://github.com/janraasch/hugo-bearblog",
PreferTitle: true, PreferTitle: true,
PostDir: "blog", PostDir: "blog",
}, },
{
ID: "terminal",
Name: "Terminal",
URL: "https://github.com/panr/hugo-theme-terminal",
PreferTitle: true,
PostDir: "posts",
},
{
ID: "yingyang",
Name: "Yingyang",
URL: "https://github.com/joway/hugo-theme-yinyang",
PreferTitle: true,
PostDir: "posts",
},
} }

View file

@ -1,14 +1,33 @@
package themes package themes
import "lmika.dev/lmika/hugo-cms/models" import (
"lmika.dev/lmika/hugo-cms/models"
"lmika.dev/pkg/modash/momap"
"slices"
"sort"
)
type Provider struct{} type Provider struct {
themes []models.ThemeMeta
themeIndex map[string]models.ThemeMeta
}
func New() *Provider { func New() *Provider {
return &Provider{} ts := slices.Clone(themes)
sort.Slice(ts, func(i, j int) bool { return ts[i].Name < ts[j].Name })
ti := momap.IndexSlice(themes, func(m models.ThemeMeta) string { return m.ID })
return &Provider{
themes: ts,
themeIndex: ti,
}
}
func (p *Provider) Themes() []models.ThemeMeta {
return p.themes
} }
func (p *Provider) Lookup(name string) (models.ThemeMeta, bool) { func (p *Provider) Lookup(name string) (models.ThemeMeta, bool) {
t, ok := themes[name] t, ok := p.themeIndex[name]
return t, ok return t, ok
} }

View file

@ -19,7 +19,7 @@ func (s *Service) WritePost(site models.Site, post models.Post) models.Job {
if err != nil { if err != nil {
return err return err
} else if rbn { } else if rbn {
return s.RebuildSite(site).Do(ctx) return s.RebuildSite(site, site).Do(ctx)
} }
if err := s.writePost(site, post); err != nil { if err := s.writePost(site, post); err != nil {

View file

@ -46,21 +46,21 @@ func (s *Service) CreateNewSite(site models.Site) models.Job {
} }
} }
func (s *Service) RebuildSite(site models.Site) models.Job { func (s *Service) RebuildSite(oldSite, newSite models.Site) models.Job {
return models.Jobs( return models.Jobs(
models.Job{ models.Job{
Do: func(ctx context.Context) error { Do: func(ctx context.Context) error {
// Teardown the existing site // Teardown the existing site
siteDir := s.hugo.SiteStagingDir(site, hugo.BaseSiteDir) siteDir := s.hugo.SiteStagingDir(oldSite, hugo.BaseSiteDir)
if err := os.RemoveAll(siteDir); err != nil { if err := os.RemoveAll(siteDir); err != nil {
return err return err
} }
return nil return nil
}, },
}, },
s.CreateNewSite(site), s.CreateNewSite(newSite),
s.WriteAllPosts(site), s.WriteAllPosts(newSite),
s.Publish(site), s.Publish(newSite),
) )
} }

View file

@ -51,6 +51,7 @@ func (s *Service) CreateSite(ctx context.Context, user models.User, name string)
OwnerUserID: user.ID, OwnerUserID: user.ID,
Title: name, Title: name,
Theme: "bear", Theme: "bear",
//Theme: "yingyang",
} }
_, ok := s.themes.Lookup(newSite.Theme) _, ok := s.themes.Lookup(newSite.Theme)
@ -78,12 +79,39 @@ func (s *Service) CreateSite(ctx context.Context, user models.User, name string)
return newSite, s.jobs.Queue(ctx, s.sb.CreateNewSite(newSite)) return newSite, s.jobs.Queue(ctx, s.sb.CreateNewSite(newSite))
} }
func (s *Service) SaveSettings(ctx context.Context, site models.Site, newSettings NewSettings) error {
_, ok := s.themes.Lookup(newSettings.Theme)
if !ok {
return errors.New("theme not found")
}
newSite := site
newSite.Title = newSettings.Title
newSite.Name = normaliseName(newSite.Title)
newSite.Theme = newSettings.Theme
if err := s.db.UpdateSite(ctx, newSite); err != nil {
return err
}
return s.jobs.Queue(ctx, s.sb.RebuildSite(site, newSite))
}
type NewSettings struct {
Title string `form:"title"`
Theme string `form:"theme"`
}
func (s *Service) Rebuild(ctx context.Context, site models.Site) error { func (s *Service) Rebuild(ctx context.Context, site models.Site) error {
if site.ID == 0 { if site.ID == 0 {
return errors.New("site id required") return errors.New("site id required")
} }
return s.jobs.Queue(ctx, s.sb.RebuildSite(site)) return s.jobs.Queue(ctx, s.sb.RebuildSite(site, site))
}
func (s *Service) Themes() []models.ThemeMeta {
return s.themes.Themes()
} }
func normaliseName(name string) string { func normaliseName(name string) string {

View file

@ -12,4 +12,11 @@ INSERT INTO sites (
theme, theme,
props props
) VALUES ($1, $2, $3, $4, $5) ) VALUES ($1, $2, $3, $4, $5)
RETURNING id; RETURNING id;
-- name: UpdateSite :exec
UPDATE sites SET
name = $2,
title = $3,
theme = $4
WHERE id = $1;

View file

@ -6,4 +6,5 @@ import "embed"
//go:embed auth/*.html //go:embed auth/*.html
//go:embed layouts/*.html //go:embed layouts/*.html
//go:embed posts/*.html //go:embed posts/*.html
//go:embed sites/*.html
var FS embed.FS var FS embed.FS

View file

@ -12,7 +12,7 @@
<header> <header>
<h1>Hugo CMS</h1> <h1>Hugo CMS</h1>
<nav> <nav>
<span>{{.site.Name}}</span> <span>{{.site.Title}}</span>
{{ if .prodTarget }} {{ if .prodTarget }}
<a href="{{.prodTarget.URL}}" target="_blank">Visit</a> <a href="{{.prodTarget.URL}}" target="_blank">Visit</a>
{{ end }} {{ end }}

View file

@ -0,0 +1,21 @@
<form method="POST" action="/sites/{{.site.ID}}/settings">
<p>
<label>Title</label>
<input type="text" name="title" value="{{.site.Title}}" />
</p>
<p>
<label>Theme</label>
<select name="theme">
{{range .themes}}
{{if eq .ID $.site.Theme}}
<option value="{{.ID}}" selected>{{.Name}}</option>
{{else}}
<option value="{{.ID}}">{{.Name}}</option>
{{end}}
{{end}}
</select>
</p>
<p>
<input type="submit" value="Save" />
</p>
</form>