From 3e5709e4986fcca4abf9f57328950076e5ee6373 Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Wed, 8 Nov 2023 08:21:45 +0600 Subject: [PATCH 1/7] :wrench: Added ThemeDir config --- internal/domain/config.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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"` } From 72eb8627ee21830e35b2e5a6f5f04e8360107db9 Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Wed, 8 Nov 2023 08:22:07 +0600 Subject: [PATCH 2/7] :technologist: Check and create theme dir if not exist --- main.go | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/main.go b/main.go index 8e28f5e..4df6102 100644 --- a/main.go +++ b/main.go @@ -49,21 +49,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) + } } } } From c61ab4d92938885967cf6aeeedbe9272c07334f4 Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Wed, 8 Nov 2023 08:50:44 +0600 Subject: [PATCH 3/7] :card_file_box: Created sample theme repository interface --- internal/theme/repository.go | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 internal/theme/repository.go 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) +} From 0e73130b78113ccb1b10b33f995c92fe05f27372 Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Wed, 8 Nov 2023 08:51:55 +0600 Subject: [PATCH 4/7] :card_file_box: Created simple theme FileSystem repository implementation --- internal/theme/repository/fs/fs_theme.go | 29 ++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 internal/theme/repository/fs/fs_theme.go 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 +} From c3245c358818e92622e58e4eaa677d6e8d9240b6 Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Wed, 8 Nov 2023 08:53:17 +0600 Subject: [PATCH 5/7] :necktie: Created sample theme use case interface --- internal/theme/usecase.go | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 internal/theme/usecase.go 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) +} From e71a84bb074021e620d4787a0904d59ad619908d Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Wed, 8 Nov 2023 08:53:32 +0600 Subject: [PATCH 6/7] :necktie: Created basic theme use case implementation --- internal/theme/usecase/theme_ucase.go | 28 +++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 internal/theme/usecase/theme_ucase.go 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 +} From 94e4691734a707ce938df19ac0bd3a9143666f49 Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Wed, 8 Nov 2023 08:55:27 +0600 Subject: [PATCH 7/7] :building_construction: Use theme templates instead embed Embed templates must be used for internal pages, dashboard and other non-customizable views --- main.go | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index 4df6102..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) @@ -78,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) @@ -109,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 },