cerrado @ 2dd4cf35aab8324608a83d337459fd8354521b92

feat: Wraps handler into its own package

Although this creates more complex folder structure will allow in the
feature for a easier testing of those given handlers.
  1diff --git a/main.go b/main.go
  2index 76da07bd4f55d968e2d8de6fde919aaca7325301..eedff5eb6a12149528573530facbba8b740bd578 100644
  3--- a/main.go
  4+++ b/main.go
  5@@ -10,7 +10,6 @@ 	"os/signal"
  6 	"time"
  7 
  8 	"git.gabrielgio.me/cerrado/pkg/config"
  9-	"git.gabrielgio.me/cerrado/pkg/git"
 10 	"git.gabrielgio.me/cerrado/pkg/handler"
 11 	"git.gabrielgio.me/cerrado/pkg/service"
 12 	"git.gabrielgio.me/cerrado/pkg/worker"
 13@@ -33,38 +32,21 @@ 	)
 14 
 15 	flag.Parse()
 16 
 17-	mux := http.NewServeMux()
 18-
 19-	staticHandler, err := handler.NewStaticHander("/static/")
 20+	// repositorie
 21+	configRepo, err := config.LoadConfigurationRepository(*configPath)
 22 	if err != nil {
 23 		return err
 24 	}
 25 
 26-	f, err := os.Open(*configPath)
 27-	if err != nil {
 28-		return err
 29-	}
 30+	// services
 31+	gitService := service.NewGitService(configRepo)
 32 
 33-	config, err := config.Parse(f)
 34+	handler, err := handler.MountHandler(gitService, configRepo)
 35 	if err != nil {
 36 		return err
 37 	}
 38 
 39-	// repositories
 40-	gitServer := git.NewGitServerRepository(config.Scan.Path)
 41-
 42-	// services
 43-	gitService := service.NewGitService(gitServer)
 44-
 45-	//handlers
 46-	gitHandler := handler.NewGitHandler(gitService)
 47-	aboutHandler := handler.NewAboutHandler(config.RootReadme)
 48-
 49-	mux.Handle("/static/", staticHandler)
 50-	mux.HandleFunc("/config", handler.ConfigFile(*configPath))
 51-	mux.HandleFunc("/about", aboutHandler.About)
 52-	mux.HandleFunc("/", gitHandler.List)
 53-	serverTask := worker.NewServerTask(&http.Server{Handler: mux, Addr: "0.0.0.0:8080"})
 54+	serverTask := worker.NewServerTask(&http.Server{Handler: handler, Addr: "0.0.0.0:8080"})
 55 
 56 	pool := worker.NewTaskPool()
 57 	pool.AddTask("http-server", 5*time.Second, serverTask)
 58diff --git a/pkg/config/config.go b/pkg/config/config.go
 59index 9b6accea2b385fa1180aec54df7e0a64bb3ee6fe..419d49dac3a06f7357de72162885c2b822be8e07 100644
 60--- a/pkg/config/config.go
 61+++ b/pkg/config/config.go
 62@@ -1,27 +1,126 @@
 63 package config
 64 
 65 import (
 66+	"errors"
 67 	"fmt"
 68 	"io"
 69+	"os"
 70+	"path"
 71 	"strconv"
 72 
 73 	"git.gabrielgio.me/cerrado/pkg/u"
 74 	"git.sr.ht/~emersion/go-scfg"
 75+)
 76+
 77+var (
 78+	ScanPathErr = errors.New("Scan path does not exist")
 79+	RepoPathErr = errors.New("Repository path does not exist")
 80 )
 81 
 82 type (
 83-	Scan struct {
 84+
 85+	// scan represents piece of the scan from the configuration file.
 86+	scan struct {
 87 		Path   string
 88 		Public bool
 89 	}
 90 
 91-	Configuration struct {
 92-		Scan       *Scan
 93+	// configuration represents file configuration.
 94+	configuration struct {
 95+		Scan       *scan
 96 		RootReadme string
 97 	}
 98+
 99+	// This is a per repository configuration.
100+	GitRepositoryConfiguration struct {
101+		Name   string
102+		Path   string
103+		Public bool
104+	}
105+
106+	// ConfigurationRepository represents the configuration repository (as in
107+	// database repositories).
108+	// This holds all the function necessary to ask for configuration
109+	// information.
110+	ConfigurationRepository struct {
111+		rootReadme   string
112+		repositories []*GitRepositoryConfiguration
113+	}
114 )
115 
116-func Parse(r io.Reader) (*Configuration, error) {
117+func LoadConfigurationRepository(configPath string) (*ConfigurationRepository, error) {
118+	f, err := os.Open(configPath)
119+	if err != nil {
120+		return nil, err
121+	}
122+
123+	config, err := parse(f)
124+	if err != nil {
125+		return nil, err
126+	}
127+
128+	repo := &ConfigurationRepository{
129+		rootReadme: config.RootReadme,
130+	}
131+
132+	err = repo.expandOnScanPath(config.Scan.Path, config.Scan.Public)
133+	if err != nil {
134+		return nil, err
135+	}
136+	return repo, nil
137+
138+}
139+
140+// GetRootReadme returns root read path
141+func (c *ConfigurationRepository) GetRootReadme() string {
142+	return c.rootReadme
143+}
144+
145+// GetByName returns configuration of repository for a given name.
146+// It returns nil if there is not match for it.
147+func (c *ConfigurationRepository) GetByName(name string) *GitRepositoryConfiguration {
148+	for _, r := range c.repositories {
149+		if r.Name == name {
150+			return r
151+		}
152+	}
153+	return nil
154+}
155+
156+// List returns all the configuration for all repositories.
157+func (c *ConfigurationRepository) List() []*GitRepositoryConfiguration {
158+	return c.repositories
159+}
160+
161+// expandOnScanPath scans the scanPath for folders taking them as repositories
162+// and applying them default configuration.
163+func (c *ConfigurationRepository) expandOnScanPath(scanPath string, public bool) error {
164+	if !u.FileExist(scanPath) {
165+		return ScanPathErr
166+	}
167+
168+	entries, err := os.ReadDir(scanPath)
169+	if err != nil {
170+		return err
171+	}
172+
173+	c.repositories = make([]*GitRepositoryConfiguration, 0)
174+	for _, e := range entries {
175+		if !e.IsDir() {
176+			continue
177+		}
178+
179+		fullPath := path.Join(scanPath, e.Name())
180+		c.repositories = append(c.repositories, &GitRepositoryConfiguration{
181+			Name:   e.Name(),
182+			Path:   fullPath,
183+			Public: public,
184+		})
185+	}
186+	return nil
187+}
188+
189+func parse(r io.Reader) (*configuration, error) {
190 	block, err := scfg.Read(r)
191 	if err != nil {
192 		return nil, err
193@@ -42,9 +141,9 @@
194 	return config, nil
195 }
196 
197-func defaultConfiguration() *Configuration {
198-	return &Configuration{
199-		Scan: &Scan{
200+func defaultConfiguration() *configuration {
201+	return &configuration{
202+		Scan: &scan{
203 			Public: true,
204 			Path:   "",
205 		},
206@@ -57,7 +156,7 @@ 	scanDir := block.Get("root-readme")
207 	return setString(scanDir, readme)
208 }
209 
210-func setScan(block scfg.Block, scan *Scan) error {
211+func setScan(block scfg.Block, scan *scan) error {
212 	scanDir := block.Get("scan")
213 	err := setString(scanDir, &scan.Path)
214 	if err != nil {
215diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go
216index c8cd8876055e2cabe66e1ee574b5c36df358efcb..7afbaef263f86b987b586cc5cb70cc6488a2d994 100644
217--- a/pkg/config/config_test.go
218+++ b/pkg/config/config_test.go
219@@ -8,17 +8,17 @@
220 	"github.com/google/go-cmp/cmp"
221 )
222 
223-func TestConfig(t *testing.T) {
224+func TestFileParsing(t *testing.T) {
225 	testCases := []struct {
226 		name           string
227 		config         string
228-		expectedConfig *Configuration
229+		expectedConfig *configuration
230 	}{
231 		{
232 			name:   "minimal scan",
233 			config: `scan "/srv/git"`,
234-			expectedConfig: &Configuration{
235-				Scan: &Scan{
236+			expectedConfig: &configuration{
237+				Scan: &scan{
238 					Public: true,
239 					Path:   "/srv/git",
240 				},
241@@ -29,8 +29,8 @@ 			name: "complete scan",
242 			config: `scan "/srv/git" {
243 	public false
244 }`,
245-			expectedConfig: &Configuration{
246-				Scan: &Scan{
247+			expectedConfig: &configuration{
248+				Scan: &scan{
249 					Public: false,
250 					Path:   "/srv/git",
251 				},
252@@ -41,7 +41,7 @@
253 	for _, tc := range testCases {
254 		t.Run(tc.name, func(t *testing.T) {
255 			r := strings.NewReader(tc.config)
256-			config, err := Parse(r)
257+			config, err := parse(r)
258 			if err != nil {
259 				t.Fatalf("Error parsing config %s", err.Error())
260 			}
261diff --git a/pkg/git/git.go b/pkg/git/git.go
262index 85a3b9560ac63e1915751dc8407365e72539c8fc..b9ab235f7226301c7f57ca245c5b1660fa6230b9 100644
263--- a/pkg/git/git.go
264+++ b/pkg/git/git.go
265@@ -2,63 +2,29 @@ package git
266 
267 import (
268 	"errors"
269-	"os"
270-	"path"
271 
272-	"git.gabrielgio.me/cerrado/pkg/u"
273 	"github.com/go-git/go-git/v5"
274 	"github.com/go-git/go-git/v5/plumbing/object"
275 )
276 
277+var ()
278+
279 var (
280-	ScanPathErr    = errors.New("Scan path does not exist")
281-	RepoPathErr    = errors.New("Repository path does not exist")
282-	missingHeadErr = errors.New("Head not found")
283+	MissingHeadErr = errors.New("Head not found")
284 )
285 
286 type (
287-	GitServerRepository struct {
288-		scanPath string
289-	}
290-
291 	GitRepository struct {
292 		path string
293 	}
294 )
295 
296-func NewGitServerRepository(scanPath string) *GitServerRepository {
297-	return &GitServerRepository{scanPath}
298-}
299-
300 func NewGitRepository(dir string) *GitRepository {
301 	return &GitRepository{
302 		path: dir,
303 	}
304 }
305 
306-func (g *GitServerRepository) List() ([]*GitRepository, error) {
307-	if !u.FileExist(g.scanPath) {
308-		return nil, ScanPathErr
309-	}
310-
311-	entries, err := os.ReadDir(g.scanPath)
312-	if err != nil {
313-		return nil, err
314-	}
315-
316-	repos := make([]*GitRepository, 0)
317-	for _, e := range entries {
318-		if !e.IsDir() {
319-			continue
320-		}
321-
322-		fullPath := path.Join(g.scanPath, e.Name())
323-		repos = append(repos, NewGitRepository(fullPath))
324-	}
325-
326-	return repos, nil
327-}
328-
329 func (g *GitRepository) Path() string {
330 	return g.path
331 }
332@@ -71,7 +37,7 @@ 	}
333 
334 	ref, err := repo.Head()
335 	if err != nil {
336-		return nil, errors.Join(missingHeadErr, err)
337+		return nil, errors.Join(MissingHeadErr, err)
338 	}
339 
340 	c, err := repo.CommitObject(ref.Hash())
341diff --git a/pkg/handler/about.go b/pkg/handler/about/handler.go
342rename from pkg/handler/about.go
343rename to pkg/handler/about/handler.go
344index 3ab2de880549e94be04543d4a0d4698d5d87f2b5..a2caa4eeea02e1e6885069fc90f0d1ef479b4798 100644
345--- a/pkg/handler/about.go
346+++ b/pkg/handler/about/handler.go
347@@ -1,4 +1,4 @@
348-package handler
349+package about
350 
351 import (
352 	"io"
353@@ -13,12 +13,18 @@
354 	"git.gabrielgio.me/cerrado/templates"
355 )
356 
357-type AboutHandler struct {
358-	readmePath string
359-}
360+type (
361+	AboutHandler struct {
362+		readmePath string
363+	}
364 
365-func NewAboutHandler(readmePath string) *AboutHandler {
366-	return &AboutHandler{readmePath}
367+	configurationRepository interface {
368+		GetRootReadme() string
369+	}
370+)
371+
372+func NewAboutHandler(configRepo configurationRepository) *AboutHandler {
373+	return &AboutHandler{configRepo.GetRootReadme()}
374 }
375 
376 func (g *AboutHandler) About(w http.ResponseWriter, _ *http.Request) {
377diff --git a/pkg/handler/git.go b/pkg/handler/git/handler.go
378rename from pkg/handler/git.go
379rename to pkg/handler/git/handler.go
380index 1ed2c49d357ffbb4b74f2a2331cd84fd6a660794..236ac414a2ce8afb92d6c86bc64781a7f8189904 100644
381--- a/pkg/handler/git.go
382+++ b/pkg/handler/git/handler.go
383@@ -1,4 +1,4 @@
384-package handler
385+package git
386 
387 import (
388 	"log/slog"
389@@ -8,12 +8,20 @@ 	"git.gabrielgio.me/cerrado/pkg/service"
390 	"git.gabrielgio.me/cerrado/templates"
391 )
392 
393-type GitHandler struct {
394-	gitService *service.GitService
395-}
396+type (
397+	GitHandler struct {
398+		gitService gitService
399+	}
400 
401-func NewGitHandler(gitService *service.GitService) *GitHandler {
402-	return &GitHandler{gitService}
403+	gitService interface {
404+		ListRepositories() ([]*service.Repository, error)
405+	}
406+)
407+
408+func NewGitHandler(gitService gitService) *GitHandler {
409+	return &GitHandler{
410+		gitService: gitService,
411+	}
412 }
413 
414 func (g *GitHandler) List(w http.ResponseWriter, _ *http.Request) {
415diff --git a/pkg/handler/router.go b/pkg/handler/router.go
416new file mode 100644
417index 0000000000000000000000000000000000000000..a8c9c6f227152e0602af3786f023508c563419d0
418--- /dev/null
419+++ b/pkg/handler/router.go
420@@ -0,0 +1,38 @@
421+package handler
422+
423+import (
424+	"net/http"
425+
426+	serverconfig "git.gabrielgio.me/cerrado/pkg/config"
427+	"git.gabrielgio.me/cerrado/pkg/handler/about"
428+	"git.gabrielgio.me/cerrado/pkg/handler/config"
429+	"git.gabrielgio.me/cerrado/pkg/handler/git"
430+	"git.gabrielgio.me/cerrado/pkg/handler/static"
431+	"git.gabrielgio.me/cerrado/pkg/service"
432+)
433+
434+// Mount handler gets the requires service and repository to build the handlers
435+// This functons wraps the whole handler package and wraps it into one part so
436+// its sub package don't leak in other places.
437+func MountHandler(
438+	gitService *service.GitService,
439+	configRepo *serverconfig.ConfigurationRepository,
440+) (http.Handler, error) {
441+	var (
442+		gitHandler   = git.NewGitHandler(gitService)
443+		aboutHandler = about.NewAboutHandler(configRepo)
444+		configHander = config.ConfigFile(configRepo)
445+	)
446+
447+	staticHandler, err := static.NewStaticHander("/static/")
448+	if err != nil {
449+		return nil, err
450+	}
451+
452+	mux := http.NewServeMux()
453+	mux.Handle("/static/", staticHandler)
454+	mux.HandleFunc("/config", configHander)
455+	mux.HandleFunc("/about", aboutHandler.About)
456+	mux.HandleFunc("/", gitHandler.List)
457+	return mux, nil
458+}
459diff --git a/pkg/handler/static.go b/pkg/handler/static/handler.go
460rename from pkg/handler/static.go
461rename to pkg/handler/static/handler.go
462index 9f312f41f09ef14dad4649bfaa0b76d61f8abb5e..6a826cc988aa4d11f5f71417b4a3198a7d611882 100644
463--- a/pkg/handler/static.go
464+++ b/pkg/handler/static/handler.go
465@@ -1,4 +1,4 @@
466-package handler
467+package static
468 
469 import (
470 	"io/fs"
471diff --git a/pkg/handler/status.go b/pkg/handler/config/handler.go
472rename from pkg/handler/status.go
473rename to pkg/handler/config/handler.go
474index 9baac2c47a3fce61a82cc9451ecc92e8de179c87..c278e35d0e624ac09c1a0065f0f906bd6e88dfe1 100644
475--- a/pkg/handler/status.go
476+++ b/pkg/handler/config/handler.go
477@@ -1,11 +1,10 @@
478-package handler
479+package config
480 
481 import (
482 	"bytes"
483 	"encoding/json"
484 	"log/slog"
485 	"net/http"
486-	"os"
487 
488 	"github.com/alecthomas/chroma/v2/formatters/html"
489 	"github.com/alecthomas/chroma/v2/lexers"
490@@ -15,21 +14,25 @@ 	"git.gabrielgio.me/cerrado/pkg/config"
491 	"git.gabrielgio.me/cerrado/templates"
492 )
493 
494-func ConfigFile(configPath string) func(http.ResponseWriter, *http.Request) {
495+type (
496+	configurationRepository interface {
497+		GetRootReadme() string
498+		List() []*config.GitRepositoryConfiguration
499+	}
500+)
501+
502+func ConfigFile(configRepo configurationRepository) func(http.ResponseWriter, *http.Request) {
503 	return func(w http.ResponseWriter, _ *http.Request) {
504-		f, err := os.Open(configPath)
505-		if err != nil {
506-			slog.Error("Error openning config file", "error", err, "path", configPath)
507-			return
508-		}
509 
510-		c, err := config.Parse(f)
511-		if err != nil {
512-			slog.Error("Error parsing config", "error", err, "path", configPath)
513-			return
514+		config := struct {
515+			RootReadme   string
516+			Repositories []*config.GitRepositoryConfiguration
517+		}{
518+			RootReadme:   configRepo.GetRootReadme(),
519+			Repositories: configRepo.List(),
520 		}
521 
522-		b, err := json.MarshalIndent(c, "", "	")
523+		b, err := json.MarshalIndent(config, "", "  ")
524 		if err != nil {
525 			slog.Error("Error parsing json", "error", err)
526 			return
527diff --git a/pkg/service/git.go b/pkg/service/git.go
528index 94ca75e454f3af3b40f834ea5f858b37b6e56efd..2b1fe252f8e7785238896329ceca3305c71b817c 100644
529--- a/pkg/service/git.go
530+++ b/pkg/service/git.go
531@@ -3,44 +3,48 @@
532 import (
533 	"path"
534 
535+	"git.gabrielgio.me/cerrado/pkg/config"
536 	"git.gabrielgio.me/cerrado/pkg/git"
537 )
538 
539 type (
540-	GitService struct {
541-		server *git.GitServerRepository
542-	}
543 	Repository struct {
544 		Name              string
545 		Title             string
546 		LastCommitMessage string
547 		LastCommitDate    string
548 	}
549+
550+	GitService struct {
551+		configRepo configurationRepository
552+	}
553+
554+	configurationRepository interface {
555+		List() []*config.GitRepositoryConfiguration
556+	}
557 )
558 
559 // TODO: make it configurable
560 const timeFormat = "2006.01.02 15:04:05"
561 
562-func NewGitService(server *git.GitServerRepository) *GitService {
563+func NewGitService(configRepo configurationRepository) *GitService {
564 	return &GitService{
565-		server: server,
566+		configRepo: configRepo,
567 	}
568 }
569 
570 func (g *GitService) ListRepositories() ([]*Repository, error) {
571-	rs, err := g.server.List()
572-	if err != nil {
573-		return nil, err
574-	}
575+	rs := g.configRepo.List()
576 
577 	repos := make([]*Repository, len(rs))
578 	for i, r := range rs {
579-		obj, err := r.LastCommit()
580+		repo := git.NewGitRepository(r.Path)
581+		obj, err := repo.LastCommit()
582 		if err != nil {
583 			return nil, err
584 		}
585 
586-		baseName := path.Base(r.Path())
587+		baseName := path.Base(r.Path)
588 		repos[i] = &Repository{
589 			Name:              baseName,
590 			Title:             baseName,