package routes import ( "compress/gzip" "fmt" "log" "path/filepath" "strconv" "strings" "alin.ovh/elgit/config" "alin.ovh/elgit/data" "alin.ovh/elgit/git" "alin.ovh/elgit/templates" "github.com/microcosm-cc/bluemonday" blackfriday "github.com/russross/blackfriday/v2" "github.com/savsgio/atreugo/v11" "github.com/valyala/fasthttp" ) type deps struct { c *config.Config projects []string repos *data.Entries } func (d *deps) Index(rc *atreugo.RequestCtx) error { pageData := templates.PageData{ Meta: d.c.Meta, } rc.SetContentType("text/html; charset=utf-8") return templates.IndexPage(pageData, d.repos).Render(rc) } func (d *deps) RepoIndex(rc *atreugo.RequestCtx) error { repoName, _ := rc.UserValue("repoName").(string) gr, found := d.repos.BySlug[repoName] if !found { return d.NotFound(rc) } pageData := templates.PageData{ Meta: d.c.Meta, Name: repoName, Ref: gr.MainBranch, Description: gr.Description, Servername: d.c.Server.Name, Gomod: gr.Gomod, } rc.SetContentType("text/html; charset=utf-8") return templates.RepoPage(pageData, gr.LastCommits, gr.ReadmeContent).Render(rc) } func (d *deps) RepoTree(rc *atreugo.RequestCtx) error { repoName, _ := rc.UserValue("repoName").(string) rest, _ := rc.UserValue("rest").(string) treePath := strings.TrimSuffix(rest, "/") ref := rc.UserValue("ref").(string) gr, found := d.repos.BySlug[repoName] if !found { return d.NotFound(rc) } files, err := gr.FileTree(treePath) if err != nil { return err } rc.SetContentType("text/html; charset=utf-8") pageData := templates.PageData{ Meta: d.c.Meta, Name: repoName, Ref: ref, Description: gr.Description, Parent: treePath, } return templates.TreePage(pageData, files, gr.ReadmeContent, filepath.Dir(treePath)).Render(rc) } func (d *deps) FileContent(rc *atreugo.RequestCtx) error { var raw bool if rawParam, err := strconv.ParseBool(string(rc.Request.URI().QueryArgs().Peek("raw"))); err == nil { raw = rawParam } repoName, _ := rc.UserValue("repoName").(string) treePath := rc.UserValue("rest").(string) ref := rc.UserValue("ref").(string) gr, found := d.repos.BySlug[repoName] if !found { return d.NotFound(rc) } contents, err := gr.FileContent(treePath) if err != nil { return err } if raw { return rc.TextResponse(contents) } rc.SetContentType("text/html; charset=utf-8") pageData := templates.PageData{ Meta: d.c.Meta, Name: repoName, Ref: ref, Description: gr.Description, Path: treePath, Content: contents, } if len(pageData.Content) > 0 { switch filepath.Ext(pageData.Path) { case ".md": unsafe := blackfriday.Run( []byte(pageData.Content), blackfriday.WithExtensions(blackfriday.CommonExtensions), ) html := bluemonday.UGCPolicy().SanitizeBytes(unsafe) pageData.RenderedContent = string(html) default: safe := bluemonday.UGCPolicy().SanitizeBytes([]byte(pageData.Content)) pageData.RenderedContent = fmt.Sprintf(`
%s
`, safe) } } lc, err := countLines(strings.NewReader(pageData.Content)) if err != nil { log.Printf("counting lines: %s", err) } lines := make([]int, lc) if lc > 0 { for i := range lines { lines[i] = i + 1 } } pageData.LineCount = lines return templates.FilePage(pageData).Render(rc) } func (d *deps) Archive(rc *atreugo.RequestCtx) error { repoName, _ := rc.UserValue("repoName").(string) repoPath, _ := rc.UserValue("repoPath").(string) file := rc.UserValue("file").(string) if !strings.HasSuffix(file, ".tar.gz") { return d.NotFound(rc) } ref := strings.TrimSuffix(file, ".tar.gz") // This allows the browser to use a proper name for the file when downloading filename := fmt.Sprintf("%s-%s.tar.gz", repoName, ref) setContentDisposition(rc, filename) setGZipMIME(rc) gr, err := git.Open(repoPath, ref) if err != nil { return d.NotFound(rc) } gw := gzip.NewWriter(rc) defer func() { err := gw.Close() if err != nil { log.Printf("failed to close gzip writer: %s", err) } }() prefix := fmt.Sprintf("%s-%s", repoName, ref) err = gr.WriteTar(gw, prefix) if err != nil { // once we start writing to the body we can't report error anymore // so we are only left with printing the error. log.Println(err) return nil } err = gw.Flush() if err != nil { // once we start writing to the body we can't report error anymore // so we are only left with printing the error. log.Println(err) } return nil } func (d *deps) Log(rc *atreugo.RequestCtx) error { repoName, _ := rc.UserValue("repoName").(string) ref := rc.UserValue("ref").(string) gr, found := d.repos.BySlug[repoName] if !found { return d.NotFound(rc) } commits, err := gr.Commits() if err != nil { return err } pageData := templates.PageData{ Meta: d.c.Meta, Name: repoName, Ref: ref, Description: gr.Description, Log: true, } rc.SetContentType("text/html; charset=utf-8") return templates.LogPage(pageData, commits).Render(rc) } func (d *deps) Diff(rc *atreugo.RequestCtx) error { repoName, _ := rc.UserValue("repoName").(string) ref := rc.UserValue("ref").(string) gr, found := d.repos.BySlug[repoName] if !found { return d.NotFound(rc) } diff, err := gr.Diff() if err != nil { return err } pageData := templates.PageData{ Meta: d.c.Meta, Name: repoName, Stat: diff.Stat, Diff: diff.Diff, Ref: ref, Description: gr.Description, } rc.SetContentType("text/html; charset=utf-8") return templates.CommitPage(pageData, diff).Render(rc) } // FileDiff shows the changes to a specific file in a commit func (d *deps) FileDiff(rc *atreugo.RequestCtx) error { repoName, _ := rc.UserValue("repoName").(string) ref := rc.UserValue("ref").(string) filePath := strings.TrimSuffix(rc.UserValue("file").(string), "/") g, found := d.repos.BySlug[repoName] if !found { return d.NotFound(rc) } diff, err := g.DiffFile(filePath) if err != nil { return err } pageData := templates.PageData{ Meta: d.c.Meta, Name: repoName, Ref: ref, Description: g.Description, Path: filePath, Diff: diff.Diff, } rc.SetContentType("text/html; charset=utf-8") return templates.CommitPage(pageData, diff).Render(rc) } func (d *deps) Refs(rc *atreugo.RequestCtx) error { repoName, _ := rc.UserValue("repoName").(string) gr, found := d.repos.BySlug[repoName] if !found { return d.NotFound(rc) } tags, err := gr.Tags() if err != nil { // Non-fatal, we *should* have at least one branch to show. log.Println(err) } branches, err := gr.Branches() if err != nil { return err } pageData := templates.PageData{ Meta: d.c.Meta, Name: repoName, Description: gr.Description, } rc.SetContentType("text/html; charset=utf-8") return templates.RefsPage(pageData, branches, tags).Render(rc) } func (d *deps) NotFound(rc *atreugo.RequestCtx) error { log.Printf("Not found: %s", rc.Request.RequestURI()) return ErrNotFound } func (d *deps) Error(rc *atreugo.RequestCtx, cause error, statusCode int) { log.Printf("Error: %v", cause) var err error if rc.Request.Header.HasAcceptEncoding("text/html") { rc.SetContentType("text/html; charset=utf-8") err = templates.ErrorPage(templates.PageData{ Error: &templates.Error{ Code: statusCode, Message: cause.Error(), }, }).Render(rc) } else { err = rc.TextResponse(cause.Error(), statusCode) } if err != nil { log.Printf("error response error: %v", err) } } func (d *deps) Panic(rc *atreugo.RequestCtx, data any) { switch data := data.(type) { case string, []byte: d.Error(rc, fmt.Errorf("panic: %s", data), fasthttp.StatusInternalServerError) case error: d.Error(rc, fmt.Errorf("panic: %w", data), fasthttp.StatusInternalServerError) } }