~bigbes/sr-ht-dolt

ref: dba72f5908d704bbe8b3e8efe35432917192292b sr-ht-dolt/web/handlers_browse.go -rw-r--r-- 5.9 KiB
dba72f59 — Eugene Blikh mcpsrv: list the memories a tracker holds 5 days ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
package web

import (
	"errors"
	"net/http"
	"strconv"

	"github.com/go-chi/chi/v5"

	"sourcecraft.dev/bigbes/sr-ht-ecore/chrome"

	"sourcecraft.dev/bigbes/sr-ht-dolt/browse"
	"sourcecraft.dev/bigbes/sr-ht-dolt/core"
)

const (
	// logPageSize is the number of commits per /log page.
	logPageSize = 20
	// rowsPageSize is the number of rows per /table page.
	rowsPageSize = 50
)

// openBrowse opens a browse session for repo, writing a 500 and returning
// ok=false on failure. The caller must Close the returned session.
func (a *app) openBrowse(w http.ResponseWriter, r *http.Request, repo *core.Repo) (BrowseSession, bool) {
	sess, err := a.cfg.Browse.Open(r.Context(), repo.Path)
	if err != nil {
		http.Error(w, "failed to open database", http.StatusInternalServerError)
		return nil, false
	}
	return sess, true
}

// handleLog renders a paginated commit log for a branch (or ref). Pages after
// the first are reached via ?from=<hash> (the nextHash from the prior page); the
// active branch is chosen with ?branch=.
func (a *app) handleLog(w http.ResponseWriter, r *http.Request) {
	repo, _, _, ok := a.loadRepoForBrowse(w, r)
	if !ok {
		return
	}
	sess, ok := a.openBrowse(w, r, repo)
	if !ok {
		return
	}
	defer sess.Close()

	branches, err := sess.Branches(r.Context())
	if err != nil {
		http.Error(w, "failed to list branches", http.StatusInternalServerError)
		return
	}

	branch := r.URL.Query().Get("branch")
	if branch == "" {
		branch = browse.DefaultBranch(branches)
	}
	fromHash := r.URL.Query().Get("from")

	commits, nextHash, err := sess.Log(r.Context(), branch, fromHash, logPageSize)
	if err != nil {
		if errors.Is(err, browse.ErrRefNotFound) {
			a.notFound(w, r)
			return
		}
		http.Error(w, "failed to read log", http.StatusInternalServerError)
		return
	}

	view := struct {
		chrome.Page
		Repo     *core.Repo
		Branches []browse.Branch
		Branch   string
		Commits  []browse.CommitInfo
		NextHash string
	}{
		Page:     a.page(r, "Log — "+repo.OwnerName+"/"+repo.Name),
		Repo:     repo,
		Branches: branches,
		Branch:   branch,
		Commits:  commits,
		NextHash: nextHash,
	}
	a.render(w, http.StatusOK, "log", view)
}

// handleCommit renders a single commit's per-table diff summary.
func (a *app) handleCommit(w http.ResponseWriter, r *http.Request) {
	repo, _, _, ok := a.loadRepoForBrowse(w, r)
	if !ok {
		return
	}
	sess, ok := a.openBrowse(w, r, repo)
	if !ok {
		return
	}
	defer sess.Close()

	hash := chi.URLParam(r, "hash")
	summary, err := sess.CommitSummary(r.Context(), hash)
	if err != nil {
		if errors.Is(err, browse.ErrRefNotFound) {
			a.notFound(w, r)
			return
		}
		http.Error(w, "failed to read commit", http.StatusInternalServerError)
		return
	}

	view := struct {
		chrome.Page
		Repo    *core.Repo
		Summary *browse.CommitDiff
	}{
		Page:    a.page(r, "Commit "+chrome.ShortSHA(hash)+" — "+repo.OwnerName+"/"+repo.Name),
		Repo:    repo,
		Summary: summary,
	}
	a.render(w, http.StatusOK, "commit", view)
}

// handleTree renders the tables (with schemas) present at a ref.
func (a *app) handleTree(w http.ResponseWriter, r *http.Request) {
	repo, _, _, ok := a.loadRepoForBrowse(w, r)
	if !ok {
		return
	}
	sess, ok := a.openBrowse(w, r, repo)
	if !ok {
		return
	}
	defer sess.Close()

	ref := chi.URLParam(r, "ref")
	tables, err := sess.Tables(r.Context(), ref)
	if err != nil {
		if errors.Is(err, browse.ErrRefNotFound) {
			a.notFound(w, r)
			return
		}
		http.Error(w, "failed to read tables", http.StatusInternalServerError)
		return
	}

	view := struct {
		chrome.Page
		Repo   *core.Repo
		Ref    string
		Tables []browse.TableInfo
		Views  []View
	}{
		Page:   a.page(r, "Tree "+ref+" — "+repo.OwnerName+"/"+repo.Name),
		Repo:   repo,
		Ref:    ref,
		Tables: tables,
		Views:  applicableViews(a.views, tables),
	}
	a.render(w, http.StatusOK, "tree", view)
}

// handleTable renders a table's schema and a paginated page of its rows. Pages
// are selected with ?page=N (1-based).
func (a *app) handleTable(w http.ResponseWriter, r *http.Request) {
	repo, _, _, ok := a.loadRepoForBrowse(w, r)
	if !ok {
		return
	}
	sess, ok := a.openBrowse(w, r, repo)
	if !ok {
		return
	}
	defer sess.Close()

	ref := chi.URLParam(r, "ref")
	table := chi.URLParam(r, "table")

	page := 1
	if p, err := strconv.Atoi(r.URL.Query().Get("page")); err == nil && p > 1 {
		page = p
	}
	offset := (page - 1) * rowsPageSize

	rows, err := sess.Rows(r.Context(), ref, table, offset, rowsPageSize)
	if err != nil {
		if errors.Is(err, browse.ErrRefNotFound) || errors.Is(err, browse.ErrTableNotFound) {
			a.notFound(w, r)
			return
		}
		http.Error(w, "failed to read rows", http.StatusInternalServerError)
		return
	}

	totalPages := (rows.Total + rowsPageSize - 1) / rowsPageSize
	if totalPages < 1 {
		totalPages = 1
	}

	// Fingerprint the tables at this ref for the tab bar; a failure here must not
	// break the row view, so it degrades to no alternative-view tabs.
	var views []View
	if tables, err := sess.Tables(r.Context(), ref); err == nil {
		views = applicableViews(a.views, tables)
	}

	// PageNum and not Page: the chrome's own Page is embedded here, and the
	// pagination counter is the page's payload, which must not shadow it.
	view := struct {
		chrome.Page
		Repo       *core.Repo
		Ref        string
		Table      string
		Rows       *browse.RowPage
		Views      []View
		PageNum    int
		TotalPages int
		HasPrev    bool
		HasNext    bool
	}{
		Page:       a.page(r, table+" — "+repo.OwnerName+"/"+repo.Name),
		Repo:       repo,
		Ref:        ref,
		Table:      table,
		Rows:       rows,
		Views:      views,
		PageNum:    page,
		TotalPages: totalPages,
		HasPrev:    page > 1,
		HasNext:    page < totalPages,
	}
	// A row page is the one full-bleed screen this service has: the column count
	// is the table's, not ours, so the centered container turns a wide table into
	// a narrow strip with a scrollbar under it.
	view.ContainerClass = "container-fluid"
	a.render(w, http.StatusOK, "table", view)
}