diff --git a/internal/domain/config.go b/internal/domain/config.go index c104faf..4672408 100644 --- a/internal/domain/config.go +++ b/internal/domain/config.go @@ -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"` } diff --git a/internal/theme/repository.go b/internal/theme/repository.go new file mode 100644 index 0000000..b961d85 --- /dev/null +++ b/internal/theme/repository.go @@ -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) +} diff --git a/internal/theme/repository/fs/fs_theme.go b/internal/theme/repository/fs/fs_theme.go new file mode 100644 index 0000000..6110ce6 --- /dev/null +++ b/internal/theme/repository/fs/fs_theme.go @@ -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 +} diff --git a/internal/theme/usecase.go b/internal/theme/usecase.go new file mode 100644 index 0000000..99c646a --- /dev/null +++ b/internal/theme/usecase.go @@ -0,0 +1,10 @@ +package theme + +import ( + "context" + "html/template" +) + +type UseCase interface { + Do(ctx context.Context) (*template.Template, error) +} diff --git a/internal/theme/usecase/theme_ucase.go b/internal/theme/usecase/theme_ucase.go new file mode 100644 index 0000000..ded3ce4 --- /dev/null +++ b/internal/theme/usecase/theme_ucase.go @@ -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 +} diff --git a/main.go b/main.go index 8e28f5e..a00876e 100644 --- a/main.go +++ b/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 },