coverage~bigbes/sr-ht-dolt3523280cweb/handlers_browse.go

Coverage
46.7% 43/92 statements
Δ
+0.0
Blob
262dba3
1 package web
2
3 import (
4 "errors"
5 "net/http"
6 "strconv"
7
8 "github.com/go-chi/chi/v5"
9
10 "sourcecraft.dev/bigbes/sr-ht-ecore/chrome"
11
12 "sourcecraft.dev/bigbes/sr-ht-dolt/browse"
13 "sourcecraft.dev/bigbes/sr-ht-dolt/core"
14 )
15
16 const (
17 // logPageSize is the number of commits per /log page.
18 logPageSize = 20
19 // rowsPageSize is the number of rows per /table page.
20 rowsPageSize = 50
21 )
22
23 // openBrowse opens a browse session for repo, writing a 500 and returning
24 // ok=false on failure. The caller must Close the returned session.
25 71 func (a *app) openBrowse(w http.ResponseWriter, r *http.Request, repo *core.Repo) (BrowseSession, bool) {
26 71 sess, err := a.cfg.Browse.Open(r.Context(), repo.Path)
27 71 if err != nil {
28 0 http.Error(w, "failed to open database", http.StatusInternalServerError)
29 0 return nil, false
30 0 }
31 71 return sess, true
32 }
33
34 // handleLog renders a paginated commit log for a branch (or ref). Pages after
35 // the first are reached via ?from=<hash> (the nextHash from the prior page); the
36 // active branch is chosen with ?branch=.
37 2 func (a *app) handleLog(w http.ResponseWriter, r *http.Request) {
38 2 repo, _, _, ok := a.loadRepoForBrowse(w, r)
39 2 if !ok {
40 1 return
41 1 }
42 1 sess, ok := a.openBrowse(w, r, repo)
43 1 if !ok {
44 0 return
45 0 }
46 1 defer sess.Close()
47 1
48 1 branches, err := sess.Branches(r.Context())
49 1 if err != nil {
50 0 http.Error(w, "failed to list branches", http.StatusInternalServerError)
51 0 return
52 0 }
53
54 1 branch := r.URL.Query().Get("branch")
55 1 if branch == "" {
56 1 branch = browse.DefaultBranch(branches)
57 1 }
58 1 fromHash := r.URL.Query().Get("from")
59 1
60 1 commits, nextHash, err := sess.Log(r.Context(), branch, fromHash, logPageSize)
61 1 if err != nil {
62 0 if errors.Is(err, browse.ErrRefNotFound) {
63 0 a.notFound(w, r)
64 0 return
65 0 }
66 0 http.Error(w, "failed to read log", http.StatusInternalServerError)
67 0 return
68 }
69
70 1 view := struct {
71 1 chrome.Page
72 1 Repo *core.Repo
73 1 Branches []browse.Branch
74 1 Branch string
75 1 Commits []browse.CommitInfo
76 1 NextHash string
77 1 }{
78 1 Page: a.page(r, "Log — "+repo.OwnerName+"/"+repo.Name),
79 1 Repo: repo,
80 1 Branches: branches,
81 1 Branch: branch,
82 1 Commits: commits,
83 1 NextHash: nextHash,
84 1 }
85 1 a.render(w, http.StatusOK, "log", view)
86 }
87
88 // handleCommit renders a single commit's per-table diff summary.
89 0 func (a *app) handleCommit(w http.ResponseWriter, r *http.Request) {
90 0 repo, _, _, ok := a.loadRepoForBrowse(w, r)
91 0 if !ok {
92 0 return
93 0 }
94 0 sess, ok := a.openBrowse(w, r, repo)
95 0 if !ok {
96 0 return
97 0 }
98 0 defer sess.Close()
99 0
100 0 hash := chi.URLParam(r, "hash")
101 0 summary, err := sess.CommitSummary(r.Context(), hash)
102 0 if err != nil {
103 0 if errors.Is(err, browse.ErrRefNotFound) {
104 0 a.notFound(w, r)
105 0 return
106 0 }
107 0 http.Error(w, "failed to read commit", http.StatusInternalServerError)
108 0 return
109 }
110
111 0 view := struct {
112 0 chrome.Page
113 0 Repo *core.Repo
114 0 Summary *browse.CommitDiff
115 0 }{
116 0 Page: a.page(r, "Commit "+chrome.ShortSHA(hash)+" — "+repo.OwnerName+"/"+repo.Name),
117 0 Repo: repo,
118 0 Summary: summary,
119 0 }
120 0 a.render(w, http.StatusOK, "commit", view)
121 }
122
123 // handleTree renders the tables (with schemas) present at a ref.
124 1 func (a *app) handleTree(w http.ResponseWriter, r *http.Request) {
125 1 repo, _, _, ok := a.loadRepoForBrowse(w, r)
126 1 if !ok {
127 1 return
128 1 }
129 0 sess, ok := a.openBrowse(w, r, repo)
130 0 if !ok {
131 0 return
132 0 }
133 0 defer sess.Close()
134 0
135 0 ref := chi.URLParam(r, "ref")
136 0 tables, err := sess.Tables(r.Context(), ref)
137 0 if err != nil {
138 0 if errors.Is(err, browse.ErrRefNotFound) {
139 0 a.notFound(w, r)
140 0 return
141 0 }
142 0 http.Error(w, "failed to read tables", http.StatusInternalServerError)
143 0 return
144 }
145
146 0 view := struct {
147 0 chrome.Page
148 0 Repo *core.Repo
149 0 Ref string
150 0 Tables []browse.TableInfo
151 0 Views []View
152 0 }{
153 0 Page: a.page(r, "Tree "+ref+" — "+repo.OwnerName+"/"+repo.Name),
154 0 Repo: repo,
155 0 Ref: ref,
156 0 Tables: tables,
157 0 Views: applicableViews(a.views, tables),
158 0 }
159 0 a.render(w, http.StatusOK, "tree", view)
160 }
161
162 // handleTable renders a table's schema and a paginated page of its rows. Pages
163 // are selected with ?page=N (1-based).
164 3 func (a *app) handleTable(w http.ResponseWriter, r *http.Request) {
165 3 repo, _, _, ok := a.loadRepoForBrowse(w, r)
166 3 if !ok {
167 1 return
168 1 }
169 2 sess, ok := a.openBrowse(w, r, repo)
170 2 if !ok {
171 0 return
172 0 }
173 2 defer sess.Close()
174 2
175 2 ref := chi.URLParam(r, "ref")
176 2 table := chi.URLParam(r, "table")
177 2
178 2 page := 1
179 2 if p, err := strconv.Atoi(r.URL.Query().Get("page")); err == nil && p > 1 {
180 0 page = p
181 0 }
182 2 offset := (page - 1) * rowsPageSize
183 2
184 2 rows, err := sess.Rows(r.Context(), ref, table, offset, rowsPageSize)
185 2 if err != nil {
186 0 if errors.Is(err, browse.ErrRefNotFound) || errors.Is(err, browse.ErrTableNotFound) {
187 0 a.notFound(w, r)
188 0 return
189 0 }
190 0 http.Error(w, "failed to read rows", http.StatusInternalServerError)
191 0 return
192 }
193
194 2 totalPages := (rows.Total + rowsPageSize - 1) / rowsPageSize
195 2 if totalPages < 1 {
196 0 totalPages = 1
197 0 }
198
199 // Fingerprint the tables at this ref for the tab bar; a failure here must not
200 // break the row view, so it degrades to no alternative-view tabs.
201 2 var views []View
202 2 if tables, err := sess.Tables(r.Context(), ref); err == nil {
203 2 views = applicableViews(a.views, tables)
204 2 }
205
206 // PageNum and not Page: the chrome's own Page is embedded here, and the
207 // pagination counter is the page's payload, which must not shadow it.
208 2 view := struct {
209 2 chrome.Page
210 2 Repo *core.Repo
211 2 Ref string
212 2 Table string
213 2 Rows *browse.RowPage
214 2 Views []View
215 2 PageNum int
216 2 TotalPages int
217 2 HasPrev bool
218 2 HasNext bool
219 2 }{
220 2 Page: a.page(r, table+" — "+repo.OwnerName+"/"+repo.Name),
221 2 Repo: repo,
222 2 Ref: ref,
223 2 Table: table,
224 2 Rows: rows,
225 2 Views: views,
226 2 PageNum: page,
227 2 TotalPages: totalPages,
228 2 HasPrev: page > 1,
229 2 HasNext: page < totalPages,
230 2 }
231 2 // A row page is the one full-bleed screen this service has: the column count
232 2 // is the table's, not ours, so the centered container turns a wide table into
233 2 // a narrow strip with a scrollbar under it.
234 2 view.ContainerClass = "container-fluid"
235 2 a.render(w, http.StatusOK, "table", view)
236 }