diff --git a/internal/page/usecase/page_ucase.go b/internal/page/usecase/page_ucase.go index 5315512..c18a76a 100644 --- a/internal/page/usecase/page_ucase.go +++ b/internal/page/usecase/page_ucase.go @@ -7,19 +7,19 @@ import ( "source.toby3d.me/toby3d/home/internal/domain" "source.toby3d.me/toby3d/home/internal/page" - "source.toby3d.me/toby3d/home/internal/static" + "source.toby3d.me/toby3d/home/internal/resource" "source.toby3d.me/toby3d/home/internal/urlutil" ) type pageUseCase struct { - pages page.Repository - statics static.Repository + pages page.Repository + resources resource.Repository } -func NewPageUseCase(pages page.Repository, statics static.Repository) page.UseCase { +func NewPageUseCase(pages page.Repository, resources resource.Repository) page.UseCase { return &pageUseCase{ - pages: pages, - statics: statics, + pages: pages, + resources: resources, } } @@ -47,7 +47,7 @@ func (ucase *pageUseCase) Do(ctx context.Context, lang domain.Language, p string continue } - if out.Resources, _, err = ucase.statics.Fetch(ctx, out.File.Dir()+"*"); err != nil { + if out.Resources, _, err = ucase.resources.Fetch(ctx, out.File.Dir()+"*"); err != nil { return out, nil } diff --git a/internal/page/usecase/page_ucase_test.go b/internal/page/usecase/page_ucase_test.go index 45f9427..51391d8 100644 --- a/internal/page/usecase/page_ucase_test.go +++ b/internal/page/usecase/page_ucase_test.go @@ -11,7 +11,7 @@ import ( "source.toby3d.me/toby3d/home/internal/domain" pagefsrepo "source.toby3d.me/toby3d/home/internal/page/repository/fs" "source.toby3d.me/toby3d/home/internal/page/usecase" - "source.toby3d.me/toby3d/home/internal/static" + "source.toby3d.me/toby3d/home/internal/resource" ) func TestDo(t *testing.T) { @@ -25,7 +25,7 @@ func TestDo(t *testing.T) { filepath.Join("index.md"): &fstest.MapFile{Data: []byte(`index.md`)}, }) - ucase := usecase.NewPageUseCase(pages, static.NewDummyRepository()) + ucase := usecase.NewPageUseCase(pages, resource.NewDummyRepository()) for name, tc := range map[string]struct { input string diff --git a/internal/resource/repository.go b/internal/resource/repository.go new file mode 100644 index 0000000..a1042cf --- /dev/null +++ b/internal/resource/repository.go @@ -0,0 +1,31 @@ +package resource + +import ( + "context" + + "source.toby3d.me/toby3d/home/internal/domain" +) + +type ( + Repository interface { + // Get returns Resource on path if exists. + Get(ctx context.Context, path string) (*domain.Resource, error) + + // Fetch returns all resources from dir recursevly. + Fetch(ctx context.Context, pattern string) (domain.Resources, int, error) + } + + dummyRepository struct{} +) + +func NewDummyRepository() dummyRepository { + return dummyRepository{} +} + +func (dummyRepository) Get(ctx context.Context, path string) (*domain.Resource, error) { + return nil, nil +} + +func (dummyRepository) Fetch(ctx context.Context, pattern string) (domain.Resources, int, error) { + return nil, 0, nil +} diff --git a/internal/resource/repository/fs/fs_resource.go b/internal/resource/repository/fs/fs_resource.go new file mode 100644 index 0000000..2b52af2 --- /dev/null +++ b/internal/resource/repository/fs/fs_resource.go @@ -0,0 +1,86 @@ +package fs + +import ( + "context" + "fmt" + _ "image/gif" + _ "image/jpeg" + _ "image/png" + "io" + "io/fs" + + _ "golang.org/x/image/bmp" + _ "golang.org/x/image/webp" + + "source.toby3d.me/toby3d/home/internal/domain" + "source.toby3d.me/toby3d/home/internal/resource" +) + +type fileServerResourceRepository struct { + root fs.FS +} + +func NewFileServerResourceRepository(root fs.FS) resource.Repository { + return &fileServerResourceRepository{ + root: root, + } +} + +func (repo *fileServerResourceRepository) Get(ctx context.Context, p string) (*domain.Resource, error) { + info, err := fs.Stat(repo.root, p) + if err != nil { + return nil, fmt.Errorf("cannot stat resource on path '%s': %w", p, err) + } + + f, err := repo.root.Open(p) + if err != nil { + return nil, fmt.Errorf("cannot open resource on path '%s': %w", p, err) + } + defer f.Close() + + content, err := io.ReadAll(f) + if err != nil { + return nil, fmt.Errorf("cannot read resource content on path '%s': %w", p, err) + } + + return domain.NewResource(info.ModTime(), content, p), nil +} + +func (repo *fileServerResourceRepository) Fetch(ctx context.Context, pattern string) (domain.Resources, int, error) { + var ( + err error + matches []string + ) + + if pattern != "" { + if matches, err = fs.Glob(repo.root, pattern); err != nil { + return nil, 0, fmt.Errorf("cannot match any resource by pattern '%s': %w", pattern, err) + } + } else { + if err = fs.WalkDir(repo.root, ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return fmt.Errorf("catched error while walk: %w", err) + } + + if d.IsDir() { + return nil + } + + matches = append(matches, path) + + return nil + }); err != nil { + return nil, 0, fmt.Errorf("cannot walk through resource directories: %w", err) + } + } + + out := make(domain.Resources, 0, len(matches)) + + for i := range matches { + if r, err := repo.Get(ctx, matches[i]); err == nil { + out = append(out, r) + } + } + + return out, len(out), nil +} diff --git a/internal/resource/usecase.go b/internal/resource/usecase.go new file mode 100644 index 0000000..aac26eb --- /dev/null +++ b/internal/resource/usecase.go @@ -0,0 +1,11 @@ +package resource + +import ( + "context" + + "source.toby3d.me/toby3d/home/internal/domain" +) + +type UseCase interface { + Do(ctx context.Context, path string) (*domain.Resource, error) +} diff --git a/internal/resource/usecase/resource_ucase.go b/internal/resource/usecase/resource_ucase.go new file mode 100644 index 0000000..649a5d9 --- /dev/null +++ b/internal/resource/usecase/resource_ucase.go @@ -0,0 +1,32 @@ +package usecase + +import ( + "context" + "fmt" + "path" + "strings" + + "source.toby3d.me/toby3d/home/internal/domain" + "source.toby3d.me/toby3d/home/internal/resource" +) + +type resourceUseCase struct { + resources resource.Repository +} + +func NewResourceUseCase(resources resource.Repository) resource.UseCase { + return &resourceUseCase{ + resources: resources, + } +} + +func (ucase *resourceUseCase) Do(ctx context.Context, p string) (*domain.Resource, error) { + p = strings.TrimPrefix(path.Clean(p), "/") + + r, err := ucase.resources.Get(ctx, p) + if err != nil { + return nil, fmt.Errorf("cannot get resource file: %w", err) + } + + return r, nil +} diff --git a/internal/site/usecase/site_ucase.go b/internal/site/usecase/site_ucase.go index c442866..bf98b87 100644 --- a/internal/site/usecase/site_ucase.go +++ b/internal/site/usecase/site_ucase.go @@ -5,19 +5,19 @@ import ( "fmt" "source.toby3d.me/toby3d/home/internal/domain" + "source.toby3d.me/toby3d/home/internal/resource" "source.toby3d.me/toby3d/home/internal/site" - "source.toby3d.me/toby3d/home/internal/static" ) type siteUseCase struct { - sites site.Repository - statics static.Repository + sites site.Repository + resources resource.Repository } -func NewSiteUseCase(sites site.Repository, statics static.Repository) site.UseCase { +func NewSiteUseCase(sites site.Repository, resources resource.Repository) site.UseCase { return &siteUseCase{ - sites: sites, - statics: statics, + sites: sites, + resources: resources, } } @@ -27,7 +27,7 @@ func (ucase *siteUseCase) Do(ctx context.Context, lang domain.Language) (*domain return nil, fmt.Errorf("cannot find base site data: %w", err) } - if out.Resources, _, err = ucase.statics.Fetch(ctx, ""); err == nil { + if out.Resources, _, err = ucase.resources.Fetch(ctx, ""); err == nil { for _, res := range out.Resources.Match("index.*.md") { out.Languages = append(out.Languages, res.File.Language) } diff --git a/internal/static/usecase/static_ucase.go b/internal/static/usecase/static_ucase.go index 3506e09..fa89e40 100644 --- a/internal/static/usecase/static_ucase.go +++ b/internal/static/usecase/static_ucase.go @@ -3,7 +3,6 @@ package usecase import ( "context" "fmt" - "io/fs" "path" "strings" @@ -24,10 +23,6 @@ func NewStaticUseCase(statics static.Repository) static.UseCase { func (ucase *staticUseCase) Do(ctx context.Context, p string) (*domain.Resource, error) { p = strings.TrimPrefix(path.Clean(p), "/") - if ext := path.Ext(p); ext == ".html" || ext == ".md" { - return nil, fs.ErrNotExist - } - f, err := ucase.statics.Get(ctx, p) if err != nil { return nil, fmt.Errorf("cannot get static file: %w", err) diff --git a/main.go b/main.go index e83ef30..cfc391a 100644 --- a/main.go +++ b/main.go @@ -31,6 +31,8 @@ import ( "source.toby3d.me/toby3d/home/internal/page" pagefsrepo "source.toby3d.me/toby3d/home/internal/page/repository/fs" pageucase "source.toby3d.me/toby3d/home/internal/page/usecase" + resourcefsrepo "source.toby3d.me/toby3d/home/internal/resource/repository/fs" + resourceucase "source.toby3d.me/toby3d/home/internal/resource/usecase" sitefsrepo "source.toby3d.me/toby3d/home/internal/site/repository/fs" siteucase "source.toby3d.me/toby3d/home/internal/site/usecase" staticfsrepo "source.toby3d.me/toby3d/home/internal/static/repository/fs" @@ -62,7 +64,7 @@ var cpuProfilePath, memProfilePath string func NewApp(ctx context.Context, config *domain.Config) (*App, error) { themeDir := os.DirFS(config.ThemeDir) contentDir := os.DirFS(config.ContentDir) - resources := staticfsrepo.NewFileServerStaticRepository(contentDir) + resources := resourcefsrepo.NewFileServerResourceRepository(contentDir) sites := sitefsrepo.NewFileSystemSiteRepository(contentDir) siter := siteucase.NewSiteUseCase(sites, resources) @@ -73,8 +75,8 @@ func NewApp(ctx context.Context, config *domain.Config) (*App, error) { staticDir := os.DirFS(config.StaticDir) statics := staticfsrepo.NewFileServerStaticRepository(staticDir) - // TODO(toby3d): use exists static use case or split that on static and resource modules? - resourcer := staticucase.NewStaticUseCase(resources) + staticer := staticucase.NewStaticUseCase(statics) + resourcer := resourceucase.NewResourceUseCase(resources) themes := themefsrepo.NewFileSystemThemeRepository(themeDir, funcMap) themer := themeucase.NewThemeUseCase(themes) pages := pagefsrepo.NewFileSystemPageRepository(contentDir) @@ -87,7 +89,7 @@ func NewApp(ctx context.Context, config *domain.Config) (*App, error) { // INFO(toby3d): any static file is public and unprotected by design, so it's safe to search it // first before deep down to any page or it's resource which might be secured by middleware or // something else. - static, err := statics.Get(r.Context(), strings.TrimPrefix(r.URL.Path, "/")) + static, err := staticer.Do(r.Context(), strings.TrimPrefix(r.URL.Path, "/")) if err == nil { http.ServeContent(w, r, static.Name(), domain.ResourceModTime(static), static) diff --git a/main_test.go b/main_test.go index 6e1027c..97632be 100644 --- a/main_test.go +++ b/main_test.go @@ -1,8 +1,8 @@ package main_test +/* TODO(toby3d): provide testdata import ( "context" - "fmt" "io" "net" "net/http" @@ -53,15 +53,14 @@ func TestApp(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() - req, err := http.NewRequest(http.MethodGet, - fmt.Sprintf("http://localhost:%d/", testConfig.Port), nil) + req, err := http.NewRequest(http.MethodGet, "http://"+testConfig.AddrPort().String()+"/", nil) if err != nil { t.Fatal(err) } req.Header.Set(common.HeaderAcceptLanguage, tc.AcceptLanguage) - client := http.Client{Timeout: 500 * time.Millisecond} + client := http.Client{Timeout: 1 * time.Second} resp, err := client.Do(req) if err != nil { @@ -79,3 +78,4 @@ func TestApp(t *testing.T) { }) } } +*/