Compare commits
13 Commits
0f909007d8
...
dd45c634e8
Author | SHA1 | Date |
---|---|---|
Maxim Lebedev | dd45c634e8 | |
Maxim Lebedev | 76a65f0e3a | |
Maxim Lebedev | 94e4691734 | |
Maxim Lebedev | e71a84bb07 | |
Maxim Lebedev | c3245c3588 | |
Maxim Lebedev | 0e73130b78 | |
Maxim Lebedev | c61ab4d929 | |
Maxim Lebedev | 72eb8627ee | |
Maxim Lebedev | 3e5709e498 | |
Maxim Lebedev | 372efb088e | |
Maxim Lebedev | 8e821d9907 | |
Maxim Lebedev | f61d06fc39 | |
Maxim Lebedev | 5b032f2c99 |
|
@ -7,8 +7,9 @@ import (
|
|||
)
|
||||
|
||||
type Config struct {
|
||||
Host string `env:"HOST" envDefault:"0.0.0.0"`
|
||||
ContentDir string `env:"CONTENT_DIR" envDefault:"content/"`
|
||||
Host string `env:"HOST" envDefault:"0.0.0.0"`
|
||||
ThemeDir string `env:"THEME_DIR" envDefault:"theme/"`
|
||||
Port uint16 `env:"PORT" envDefault:"3000"`
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import "golang.org/x/text/language"
|
|||
|
||||
type Page struct {
|
||||
Language language.Tag
|
||||
Params map[string]any
|
||||
Title string
|
||||
Content []byte
|
||||
}
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
type Site struct {
|
||||
Language language.Tag
|
||||
TimeZone *time.Location
|
||||
Params map[string]any
|
||||
Title string
|
||||
}
|
||||
|
|
|
@ -59,5 +59,6 @@ func (repo *fileSystemPageRepository) Get(ctx context.Context, lang language.Tag
|
|||
Language: lang,
|
||||
Title: data.Title,
|
||||
Content: data.Content,
|
||||
Params: data.Params,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"time"
|
||||
|
||||
"github.com/adrg/frontmatter"
|
||||
"golang.org/x/text/language"
|
||||
|
@ -15,9 +16,14 @@ import (
|
|||
|
||||
type (
|
||||
Site struct {
|
||||
Title string `yaml:"title"`
|
||||
Params map[string]any `yaml:",inline"`
|
||||
Content []byte `yaml:"-"`
|
||||
Title string `yaml:"title"`
|
||||
TimeZone TimeZone `yaml:"timeZone"`
|
||||
Params map[string]any `yaml:",inline"`
|
||||
Content []byte `yaml:"-"`
|
||||
}
|
||||
|
||||
TimeZone struct {
|
||||
*time.Location `yaml:"-"`
|
||||
}
|
||||
|
||||
fileSystemSiteRepository struct {
|
||||
|
@ -58,5 +64,32 @@ func (repo *fileSystemSiteRepository) Get(ctx context.Context, lang language.Tag
|
|||
return &domain.Site{
|
||||
Language: lang,
|
||||
Title: data.Title,
|
||||
TimeZone: data.TimeZone.Location,
|
||||
Params: data.Params,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (tz *TimeZone) UnmarshalYAML(value *yaml.Node) error {
|
||||
if value.IsZero() {
|
||||
tz.Location = time.UTC
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
loc, err := time.LoadLocation(value.Value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot parse timeZone value '%v': %w", value, err)
|
||||
}
|
||||
|
||||
tz.Location = loc
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tz TimeZone) MarshalYAML() (any, error) {
|
||||
if tz.Location == nil {
|
||||
return time.UTC.String(), nil
|
||||
}
|
||||
|
||||
return tz.Location.String(), nil
|
||||
}
|
||||
|
|
|
@ -21,19 +21,20 @@ func NewSiteUseCase(sites site.Repository) site.UseCase {
|
|||
}
|
||||
|
||||
func (ucase *siteUseCase) Do(ctx context.Context, lang language.Tag) (*domain.Site, error) {
|
||||
base, err := ucase.sites.Get(ctx, language.Und)
|
||||
out, err := ucase.sites.Get(ctx, language.Und)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot find base site data: %w", err)
|
||||
}
|
||||
|
||||
sub, err := ucase.sites.Get(ctx, lang)
|
||||
if err != nil {
|
||||
return base, nil
|
||||
return out, nil
|
||||
}
|
||||
|
||||
out := &domain.Site{Language: sub.Language}
|
||||
if sub.Title == "" {
|
||||
out.Title = base.Title
|
||||
out.Language = sub.Language
|
||||
|
||||
for k, v := range sub.Params {
|
||||
out.Params[k] = v
|
||||
}
|
||||
|
||||
return out, nil
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
package theme
|
||||
|
||||
import (
|
||||
"context"
|
||||
"html/template"
|
||||
)
|
||||
|
||||
type Repository interface {
|
||||
// TODO(toby3d): use Page context to find it's specific template.
|
||||
Get(ctx context.Context) (*template.Template, error)
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package fs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/fs"
|
||||
|
||||
"source.toby3d.me/toby3d/home/internal/theme"
|
||||
)
|
||||
|
||||
type fileSystemThemeRepository struct {
|
||||
dir fs.FS
|
||||
}
|
||||
|
||||
func NewFileSystemThemeRepository(dir fs.FS) theme.Repository {
|
||||
return &fileSystemThemeRepository{
|
||||
dir: dir,
|
||||
}
|
||||
}
|
||||
|
||||
func (repo *fileSystemThemeRepository) Get(ctx context.Context) (*template.Template, error) {
|
||||
tpl, err := template.New("").ParseFS(repo.dir, "baseof.html", "single.html")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot find baseof.html: %w", err)
|
||||
}
|
||||
|
||||
return tpl, nil
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package theme
|
||||
|
||||
import (
|
||||
"context"
|
||||
"html/template"
|
||||
)
|
||||
|
||||
type UseCase interface {
|
||||
Do(ctx context.Context) (*template.Template, error)
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package usecase
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"html/template"
|
||||
|
||||
"source.toby3d.me/toby3d/home/internal/theme"
|
||||
)
|
||||
|
||||
type themeUseCase struct {
|
||||
themes theme.Repository
|
||||
}
|
||||
|
||||
func NewThemeUseCase(themes theme.Repository) theme.UseCase {
|
||||
return &themeUseCase{
|
||||
themes: themes,
|
||||
}
|
||||
}
|
||||
|
||||
func (ucase *themeUseCase) Do(ctx context.Context) (*template.Template, error) {
|
||||
out, err := ucase.themes.Get(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot find theme: %w", err)
|
||||
}
|
||||
|
||||
return out.Lookup("baseof"), nil
|
||||
}
|
55
main.go
55
main.go
|
@ -29,9 +29,15 @@ import (
|
|||
pageucase "source.toby3d.me/toby3d/home/internal/page/usecase"
|
||||
sitefsrepo "source.toby3d.me/toby3d/home/internal/site/repository/fs"
|
||||
siteucase "source.toby3d.me/toby3d/home/internal/site/usecase"
|
||||
"source.toby3d.me/toby3d/home/web/template"
|
||||
themefsrepo "source.toby3d.me/toby3d/home/internal/theme/repository/fs"
|
||||
themeucase "source.toby3d.me/toby3d/home/internal/theme/usecase"
|
||||
)
|
||||
|
||||
type Context struct {
|
||||
Site *domain.Site
|
||||
Page *domain.Page
|
||||
}
|
||||
|
||||
var (
|
||||
config = new(domain.Config)
|
||||
logger = log.New(os.Stdout, "home\t", log.Lmsgprefix|log.LstdFlags|log.LUTC)
|
||||
|
@ -49,21 +55,26 @@ func init() {
|
|||
logger.Fatalln("cannot unmarshal configuration into domain:", err)
|
||||
}
|
||||
|
||||
if config.ContentDir, err = filepath.Abs(filepath.Clean(config.ContentDir)); err != nil {
|
||||
logger.Fatalf("cannot format '%s' content dir path into absolute path: %s", config.ContentDir, err)
|
||||
}
|
||||
|
||||
if _, err = os.Stat(config.ContentDir); err != nil {
|
||||
if errors.Is(err, os.ErrExist) {
|
||||
return
|
||||
for _, dir := range []*string{
|
||||
&config.ContentDir,
|
||||
&config.ThemeDir,
|
||||
} {
|
||||
if *dir, err = filepath.Abs(filepath.Clean(*dir)); err != nil {
|
||||
logger.Fatalf("cannot format '%s' into absolute path: %s", *dir, err)
|
||||
}
|
||||
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
logger.Fatalln("cannot check directory path for content:", err)
|
||||
}
|
||||
if _, err = os.Stat(*dir); err != nil {
|
||||
if errors.Is(err, os.ErrExist) {
|
||||
return
|
||||
}
|
||||
|
||||
if err = os.MkdirAll(config.ContentDir, os.ModePerm); err != nil {
|
||||
logger.Fatalln("cannot create directory for content:", err)
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
logger.Fatalf("cannot check '%s' path: %v", *dir, err)
|
||||
}
|
||||
|
||||
if err = os.MkdirAll(*dir, os.ModePerm); err != nil {
|
||||
logger.Fatalf("cannot create directory on '%s': %v", *dir, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -73,6 +84,10 @@ func main() {
|
|||
matcher := language.NewMatcher(message.DefaultCatalog.Languages())
|
||||
|
||||
contentDir := os.DirFS(config.ContentDir)
|
||||
themeDir := os.DirFS(config.ThemeDir)
|
||||
|
||||
themes := themefsrepo.NewFileSystemThemeRepository(themeDir)
|
||||
themer := themeucase.NewThemeUseCase(themes)
|
||||
|
||||
sites := sitefsrepo.NewFileSystemSiteRepository(contentDir)
|
||||
siter := siteucase.NewSiteUseCase(sites)
|
||||
|
@ -104,8 +119,20 @@ func main() {
|
|||
return
|
||||
}
|
||||
|
||||
tpl, err := themer.Do(r.Context())
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set(common.HeaderContentType, common.MIMETextHTMLCharsetUTF8)
|
||||
template.WriteTemplate(w, template.NewPage(template.NewBaseOf(site), page))
|
||||
if err = tpl.Execute(w, &Context{
|
||||
Site: site,
|
||||
Page: page,
|
||||
}); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}),
|
||||
ErrorLog: logger,
|
||||
BaseContext: func(ln net.Listener) context.Context { return ctx },
|
||||
|
|
Loading…
Reference in New Issue