main / internal/interface/web/browse_handler.go
4.1 KB · Go Raw
1package web
2
3import (
4 "errors"
5 "log"
6 "net/http"
7 "strings"
8
9 "github.com/go-chi/chi"
10
11 "gitgud/internal/domain"
12 "gitgud/internal/interface/web/presenter"
13 "gitgud/internal/interface/web/templates"
14)
15
16func (h *Handlers) repoTreeRoot(w http.ResponseWriter, r *http.Request) {
17 repo := h.viewRepo(w, r)
18 if repo == nil {
19 return
20 }
21 h.renderTree(w, r, repo, "", "")
22}
23
24func (h *Handlers) repoTree(w http.ResponseWriter, r *http.Request) {
25 repo := h.viewRepo(w, r)
26 if repo == nil {
27 return
28 }
29 h.renderTree(w, r, repo, chi.URLParam(r, "ref"), chi.URLParam(r, "*"))
30}
31
32func (h *Handlers) renderTree(w http.ResponseWriter, r *http.Request, repo *domain.Repository, ref, path string) {
33 ctx := r.Context()
34
35 empty, err := h.browse.IsEmpty(ctx, repo)
36 if err != nil {
37 h.writeError(w, r, err)
38 return
39 }
40 if empty {
41 render(w, r, http.StatusOK, templates.RepoHome(currentUser(ctx), repo))
42 return
43 }
44
45 if ref == "" {
46 ref = repo.DefaultBranch
47 }
48
49 branches, err := h.browse.Branches(ctx, repo)
50 if err != nil {
51 h.writeError(w, r, err)
52 return
53 }
54 entries, err := h.browse.Tree(ctx, repo, ref, path)
55 if err != nil {
56 h.writeError(w, r, err)
57 return
58 }
59
60 readme := ""
61 for _, e := range entries {
62 if !e.IsDir && strings.EqualFold(e.Name, "README.md") {
63 if blob, err := h.browse.Blob(ctx, repo, ref, e.Path); err == nil && !blob.IsBinary {
64 readme = presenter.RenderMarkdown(blob.Content)
65 }
66 break
67 }
68 }
69
70 render(w, r, http.StatusOK, templates.BrowseTree(currentUser(ctx), repo, ref, path, branches, entries, readme))
71}
72
73func (h *Handlers) repoBlob(w http.ResponseWriter, r *http.Request) {
74 repo := h.viewRepo(w, r)
75 if repo == nil {
76 return
77 }
78 ref := chi.URLParam(r, "ref")
79 path := chi.URLParam(r, "*")
80
81 blob, err := h.browse.Blob(r.Context(), repo, ref, path)
82 if err != nil {
83 h.writeError(w, r, err)
84 return
85 }
86 if ref == "" {
87 ref = repo.DefaultBranch
88 }
89
90 highlighted := ""
91 if !blob.IsBinary {
92 highlighted = presenter.Highlight(string(blob.Content), blob.Path)
93 }
94 render(w, r, http.StatusOK, templates.BrowseBlob(currentUser(r.Context()), repo, ref, blob, highlighted))
95}
96
97func (h *Handlers) repoRaw(w http.ResponseWriter, r *http.Request) {
98 repo := h.viewRepo(w, r)
99 if repo == nil {
100 return
101 }
102 blob, err := h.browse.Blob(r.Context(), repo, chi.URLParam(r, "ref"), chi.URLParam(r, "*"))
103 if err != nil {
104 h.writeError(w, r, err)
105 return
106 }
107
108 ctype := http.DetectContentType(blob.Content)
109 if !blob.IsBinary {
110 ctype = "text/plain; charset=utf-8"
111 }
112 w.Header().Set("Content-Type", ctype)
113 w.Write(blob.Content)
114}
115
116func (h *Handlers) repoCommits(w http.ResponseWriter, r *http.Request) {
117 repo := h.viewRepo(w, r)
118 if repo == nil {
119 return
120 }
121 ref := chi.URLParam(r, "ref")
122
123 commits, err := h.browse.Log(r.Context(), repo, ref, 50, 0)
124 if err != nil {
125 h.writeError(w, r, err)
126 return
127 }
128 if ref == "" {
129 ref = repo.DefaultBranch
130 }
131 render(w, r, http.StatusOK, templates.Commits(currentUser(r.Context()), repo, ref, commits))
132}
133
134func (h *Handlers) repoCommit(w http.ResponseWriter, r *http.Request) {
135 repo := h.viewRepo(w, r)
136 if repo == nil {
137 return
138 }
139 commit, diffs, err := h.browse.CommitDiff(r.Context(), repo, chi.URLParam(r, "hash"))
140 if err != nil {
141 h.writeError(w, r, err)
142 return
143 }
144 render(w, r, http.StatusOK, templates.CommitDetail(currentUser(r.Context()), repo, commit, diffs))
145}
146
147func (h *Handlers) viewRepo(w http.ResponseWriter, r *http.Request) *domain.Repository {
148 repo, err := h.browse.Repo(r.Context(), chi.URLParam(r, "owner"), chi.URLParam(r, "repo"), currentUser(r.Context()))
149 if err != nil {
150 h.notFound(w, r)
151 return nil
152 }
153 return repo
154}
155
156func (h *Handlers) writeError(w http.ResponseWriter, r *http.Request, err error) {
157 user := currentUser(r.Context())
158 switch {
159 case errors.Is(err, domain.ErrNotFound):
160 render(w, r, http.StatusNotFound, templates.NotFound(user))
161 case errors.Is(err, domain.ErrPermission):
162 render(w, r, http.StatusForbidden, templates.Forbidden(user))
163 case errors.Is(err, domain.ErrUnauthorized):
164 http.Redirect(w, r, "/login", http.StatusSeeOther)
165 default:
166 log.Printf("server error: %v", err)
167 render(w, r, http.StatusInternalServerError, templates.ServerError(user))
168 }
169}