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,