Have got site creation working
This commit is contained in:
parent
4ecc12f035
commit
f8e7ea482b
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
.idea
|
||||||
|
build/
|
22
config/config.go
Normal file
22
config/config.go
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import "path/filepath"
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
DatabaseURL string `env:"DATABASE_URL"`
|
||||||
|
|
||||||
|
DataDir string `env:"DATA_DIR"`
|
||||||
|
DataStagingDir string `env:"DATA_STAGING_DIR,default=staging"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Load() (Config, error) {
|
||||||
|
return Config{
|
||||||
|
DatabaseURL: "postgres://postgres:postgres@localhost:5432/postgres?sslmode=disable",
|
||||||
|
DataDir: "build/data",
|
||||||
|
DataStagingDir: "staging",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Config) StagingDir() string {
|
||||||
|
return filepath.Join(c.DataDir, c.DataStagingDir)
|
||||||
|
}
|
28
main.go
28
main.go
|
@ -2,18 +2,37 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"lmika.dev/lmika/hugo-crm/models"
|
"lmika.dev/lmika/hugo-crm/config"
|
||||||
"lmika.dev/lmika/hugo-crm/providers/db"
|
"lmika.dev/lmika/hugo-crm/providers/db"
|
||||||
|
"lmika.dev/lmika/hugo-crm/providers/git"
|
||||||
|
"lmika.dev/lmika/hugo-crm/providers/hugo"
|
||||||
|
"lmika.dev/lmika/hugo-crm/providers/themes"
|
||||||
|
"lmika.dev/lmika/hugo-crm/services/sites"
|
||||||
"log"
|
"log"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
dbp, err := db.New("postgres://postgres:postgres@localhost:5432/postgres?sslmode=disable")
|
cfg, err := config.Load()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dbp, err := db.New(cfg.DatabaseURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
defer dbp.Close()
|
defer dbp.Close()
|
||||||
|
|
||||||
|
hugoProvider, err := hugo.New(cfg.StagingDir())
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
gitProvider := git.New()
|
||||||
|
themesProvider := themes.New()
|
||||||
|
|
||||||
|
siteService := sites.NewService(cfg, dbp, themesProvider, gitProvider, hugoProvider)
|
||||||
|
|
||||||
log.Println("Connected to database")
|
log.Println("Connected to database")
|
||||||
if err := dbp.Migrate(context.Background()); err != nil {
|
if err := dbp.Migrate(context.Background()); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
@ -21,10 +40,7 @@ func main() {
|
||||||
|
|
||||||
log.Println("Database migrated")
|
log.Println("Database migrated")
|
||||||
|
|
||||||
if err := dbp.InsertSite(context.Background(), &models.Site{
|
if _, err := siteService.CreateSite(context.Background(), "Test site "+time.Now().Format("2006-01-02T15:04:05")); err != nil {
|
||||||
Name: "Test site",
|
|
||||||
URL: "https://www.testsite.com",
|
|
||||||
}); err != nil {
|
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,5 +3,7 @@ package models
|
||||||
type Site struct {
|
type Site struct {
|
||||||
ID int64
|
ID int64
|
||||||
Name string
|
Name string
|
||||||
|
Title string
|
||||||
URL string
|
URL string
|
||||||
|
Theme string
|
||||||
}
|
}
|
||||||
|
|
6
models/theme.go
Normal file
6
models/theme.go
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
type ThemeMeta struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
URL string `json:"repo"`
|
||||||
|
}
|
|
@ -10,7 +10,7 @@ func (db *DB) InsertSite(ctx context.Context, site *models.Site) error {
|
||||||
id, err := db.q.NewSite(ctx, dbq.NewSiteParams{
|
id, err := db.q.NewSite(ctx, dbq.NewSiteParams{
|
||||||
Name: site.Name,
|
Name: site.Name,
|
||||||
Url: site.URL,
|
Url: site.URL,
|
||||||
Theme: "default",
|
Theme: site.Theme,
|
||||||
Props: []byte("{}"),
|
Props: []byte("{}"),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
17
providers/git/provider.go
Normal file
17
providers/git/provider.go
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
package git
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Provider struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() *Provider {
|
||||||
|
return &Provider{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) Clone(ctx context.Context, url string, targetDir string) error {
|
||||||
|
return exec.CommandContext(ctx, "git", "clone", url, targetDir).Run()
|
||||||
|
}
|
8
providers/hugo/dirs.go
Normal file
8
providers/hugo/dirs.go
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
package hugo
|
||||||
|
|
||||||
|
type SiteDir string
|
||||||
|
|
||||||
|
const (
|
||||||
|
BaseSiteDir SiteDir = "base"
|
||||||
|
ThemeSiteDir SiteDir = "theme"
|
||||||
|
)
|
70
providers/hugo/provider.go
Normal file
70
providers/hugo/provider.go
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
package hugo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"lmika.dev/lmika/hugo-crm/models"
|
||||||
|
"lmika.dev/lmika/hugo-crm/providers/hugo/tmpls"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"text/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Provider struct {
|
||||||
|
stagingDir string
|
||||||
|
tmpls *template.Template
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(stagingDir string) (*Provider, error) {
|
||||||
|
ts, err := template.ParseFS(tmpls.FS, "*.tmpl")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Provider{
|
||||||
|
stagingDir: stagingDir,
|
||||||
|
tmpls: ts,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) SiteStagingDir(site models.Site, what SiteDir) string {
|
||||||
|
switch what {
|
||||||
|
case BaseSiteDir:
|
||||||
|
return filepath.Join(p.stagingDir, site.Name)
|
||||||
|
case ThemeSiteDir:
|
||||||
|
return filepath.Join(p.stagingDir, site.Name, "themes", site.Theme)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) NewSite(ctx context.Context, site models.Site) error {
|
||||||
|
log.Printf(" .. %v", p.SiteStagingDir(site, BaseSiteDir))
|
||||||
|
if err := os.MkdirAll(p.SiteStagingDir(site, BaseSiteDir), 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the new site
|
||||||
|
if err := exec.CommandContext(ctx, "hugo", "new", "site", p.SiteStagingDir(site, BaseSiteDir)).Run(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) ReconfigureSite(ctx context.Context, site models.Site) error {
|
||||||
|
// Reconfigure the site
|
||||||
|
var hugoCfg bytes.Buffer
|
||||||
|
if err := p.tmpls.ExecuteTemplate(&hugoCfg, "config.toml.tmpl", struct {
|
||||||
|
Site models.Site
|
||||||
|
}{
|
||||||
|
Site: site,
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.WriteFile(filepath.Join(p.SiteStagingDir(site, BaseSiteDir), "hugo.toml"), hugoCfg.Bytes(), 0644); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
5
providers/hugo/tmpls/config.toml.tmpl
Normal file
5
providers/hugo/tmpls/config.toml.tmpl
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
baseURL = {{.Site.URL | printf "%q"}}
|
||||||
|
languageCode = 'en-us'
|
||||||
|
title = {{.Site.Title | printf "%q"}}
|
||||||
|
|
||||||
|
theme = {{.Site.Theme | printf "%q"}}
|
6
providers/hugo/tmpls/fs.go
Normal file
6
providers/hugo/tmpls/fs.go
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
package tmpls
|
||||||
|
|
||||||
|
import "embed"
|
||||||
|
|
||||||
|
//go:embed *.tmpl
|
||||||
|
var FS embed.FS
|
10
providers/themes/meta.go
Normal file
10
providers/themes/meta.go
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
package themes
|
||||||
|
|
||||||
|
import "lmika.dev/lmika/hugo-crm/models"
|
||||||
|
|
||||||
|
var themes = map[string]models.ThemeMeta{
|
||||||
|
"bear": models.ThemeMeta{
|
||||||
|
Name: "bear",
|
||||||
|
URL: "https://github.com/janraasch/hugo-bearblog",
|
||||||
|
},
|
||||||
|
}
|
14
providers/themes/provider.go
Normal file
14
providers/themes/provider.go
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
package themes
|
||||||
|
|
||||||
|
import "lmika.dev/lmika/hugo-crm/models"
|
||||||
|
|
||||||
|
type Provider struct{}
|
||||||
|
|
||||||
|
func New() *Provider {
|
||||||
|
return &Provider{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) Lookup(name string) (models.ThemeMeta, bool) {
|
||||||
|
t, ok := themes[name]
|
||||||
|
return t, ok
|
||||||
|
}
|
91
services/sites/service.go
Normal file
91
services/sites/service.go
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
package sites
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"lmika.dev/lmika/hugo-crm/config"
|
||||||
|
"lmika.dev/lmika/hugo-crm/models"
|
||||||
|
"lmika.dev/lmika/hugo-crm/providers/db"
|
||||||
|
"lmika.dev/lmika/hugo-crm/providers/git"
|
||||||
|
"lmika.dev/lmika/hugo-crm/providers/hugo"
|
||||||
|
"lmika.dev/lmika/hugo-crm/providers/themes"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
cfg config.Config
|
||||||
|
db *db.DB
|
||||||
|
themes *themes.Provider
|
||||||
|
git *git.Provider
|
||||||
|
hugo *hugo.Provider
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewService(
|
||||||
|
cfg config.Config,
|
||||||
|
db *db.DB,
|
||||||
|
themes *themes.Provider,
|
||||||
|
git *git.Provider,
|
||||||
|
hugo *hugo.Provider,
|
||||||
|
) *Service {
|
||||||
|
return &Service{
|
||||||
|
cfg: cfg,
|
||||||
|
db: db,
|
||||||
|
git: git,
|
||||||
|
hugo: hugo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) CreateSite(ctx context.Context, name string) (models.Site, error) {
|
||||||
|
newSite := models.Site{
|
||||||
|
Name: normaliseName(name),
|
||||||
|
Title: name,
|
||||||
|
Theme: "bear",
|
||||||
|
}
|
||||||
|
|
||||||
|
themeMeta, ok := s.themes.Lookup(newSite.Theme)
|
||||||
|
if !ok {
|
||||||
|
return models.Site{}, errors.New("theme not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.db.InsertSite(ctx, &newSite); err != nil {
|
||||||
|
return models.Site{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the site
|
||||||
|
log.Printf(" .. build")
|
||||||
|
if err := s.hugo.NewSite(ctx, newSite); err != nil {
|
||||||
|
return models.Site{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup the theme
|
||||||
|
log.Printf(" .. theme")
|
||||||
|
if err := s.git.Clone(ctx, themeMeta.URL, s.hugo.SiteStagingDir(newSite, hugo.ThemeSiteDir)); err != nil {
|
||||||
|
return models.Site{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.hugo.ReconfigureSite(ctx, newSite); err != nil {
|
||||||
|
return models.Site{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return newSite, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func normaliseName(name string) string {
|
||||||
|
var sb strings.Builder
|
||||||
|
|
||||||
|
seenDash := false
|
||||||
|
for _, r := range strings.TrimSpace(name) {
|
||||||
|
if unicode.IsLetter(r) || unicode.IsNumber(r) {
|
||||||
|
sb.WriteRune(unicode.ToLower(r))
|
||||||
|
seenDash = false
|
||||||
|
} else if r == '_' || r == '-' || r == '.' || unicode.IsSpace(r) {
|
||||||
|
if !seenDash {
|
||||||
|
sb.WriteRune('-')
|
||||||
|
}
|
||||||
|
seenDash = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.String()
|
||||||
|
}
|
|
@ -4,8 +4,9 @@ SELECT * FROM site;
|
||||||
-- name: NewSite :one
|
-- name: NewSite :one
|
||||||
INSERT INTO site (
|
INSERT INTO site (
|
||||||
name,
|
name,
|
||||||
|
title,
|
||||||
url,
|
url,
|
||||||
theme,
|
theme,
|
||||||
props
|
props
|
||||||
) VALUES ($1, $2, $3, $4)
|
) VALUES ($1, $2, $3, $4, $5)
|
||||||
RETURNING id;
|
RETURNING id;
|
Loading…
Reference in a new issue