Added site setting.
This commit is contained in:
parent
39611070f8
commit
3774c903e2
|
@ -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
2
go.mod
|
@ -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
2
go.sum
|
@ -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=
|
||||||
|
|
|
@ -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
|
||||||
|
|
3
main.go
3
main.go
|
@ -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()
|
||||||
|
|
|
@ -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"`
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -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{
|
||||||
|
|
|
@ -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",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
|
@ -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
|
||||||
|
|
|
@ -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 }}
|
||||||
|
|
21
templates/sites/settings.html
Normal file
21
templates/sites/settings.html
Normal 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>
|
Loading…
Reference in a new issue