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

Coverage
75.6% 31/41 statements
Δ
+0.0
Blob
acfae64
1 package web
2
3 import (
4 "context"
5 "errors"
6 "net/http"
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 // handleView renders a specialized alternative view of a repository. The view
17 // is selected by the {view} URL slug and must both exist in a.views and Apply
18 // to the tables at the resolved ref; otherwise the request is a 404 (the view
19 // simply does not exist for this repo). The generic table browser remains
20 // reachable via the tree/table routes regardless.
21 //
22 // The ref is taken from ?ref= (default: the repo's default branch). The view's
23 // own Template() is rendered with a fixed envelope: the chrome, the repo, the
24 // ref and branch list, the applicable view tabs, and the opaque value the view
25 // produced from Build (as .Data).
26 69 func (a *app) handleView(w http.ResponseWriter, r *http.Request) {
27 69 repo, _, _, ok := a.loadRepoForBrowse(w, r)
28 69 if !ok {
29 1 return
30 1 }
31 68 sess, ok := a.openBrowse(w, r, repo)
32 68 if !ok {
33 0 return
34 0 }
35 68 defer sess.Close()
36 68
37 68 ref := r.URL.Query().Get("ref")
38 68 if ref == "" {
39 65 branches, err := sess.Branches(r.Context())
40 65 if err != nil {
41 0 http.Error(w, "failed to list branches", http.StatusInternalServerError)
42 0 return
43 0 }
44 65 ref = browse.DefaultBranch(branches)
45 }
46
47 68 tables, err := sess.Tables(r.Context(), ref)
48 68 if err != nil {
49 0 if errors.Is(err, browse.ErrRefNotFound) {
50 0 a.notFound(w, r)
51 0 return
52 0 }
53 0 http.Error(w, "failed to read tables", http.StatusInternalServerError)
54 0 return
55 }
56
57 // Find the requested view; an unknown slug or a view that does not fingerprint
58 // these tables is reported as not found — the view does not exist here.
59 68 slug := chi.URLParam(r, "view")
60 68 var view View
61 93 for _, v := range a.views {
62 93 if v.Name() == slug {
63 67 view = v
64 67 break
65 }
66 }
67 68 if view == nil || !view.Applies(tables) {
68 2 a.notFound(w, r)
69 2 return
70 2 }
71
72 66 data, err := view.Build(r.Context(), sess, repo, ref, r.URL.Query())
73 66 if err != nil {
74 0 http.Error(w, "failed to build view", http.StatusInternalServerError)
75 0 return
76 0 }
77
78 // Re-list branches for the chrome/tab bar. Cheap and keeps the ref selector
79 // consistent whether or not ?ref= was supplied.
80 65 branches, _ := sess.Branches(r.Context())
81 65
82 65 envelope := struct {
83 65 chrome.Page
84 65 Repo *core.Repo
85 65 Ref string
86 65 Branches []browse.Branch
87 65 Views []View
88 65 Head *browse.CommitInfo
89 65 Data any
90 65 // Links renders the issue ids inside stored prose as links to the database
91 65 // that owns them. It is built by the one rendering that needs it — the
92 65 // beads detail pane — and is nil for every other page here, because
93 65 // building it opens a store per database the caller may browse. A nil one
94 65 // still renders text; see beadLinks.Text.
95 65 Links *beadLinks
96 65 // Memories renders a memory's stored markdown, with its [[slug]]
97 65 // references and the issue ids in its prose linked to the databases that
98 65 // hold them. It is built by the memory view alone, for the reason Links is
99 65 // built by the detail pane alone: it opens a store per database this
100 65 // caller may browse. A nil one still renders the markdown, with every
101 65 // reference left as text; see memoryLinks.Body.
102 65 Memories *memoryLinks
103 65 }{
104 65 Page: a.page(r, view.Label()+" — "+repo.OwnerName+"/"+repo.Name),
105 65 Repo: repo,
106 65 Ref: ref,
107 65 Branches: branches,
108 65 Views: applicableViews(a.views, tables),
109 65 Head: headCommit(r.Context(), sess, ref),
110 65 Data: data,
111 65 Links: a.beadCrossLinks(r, view, repo, data),
112 65 Memories: a.memoryCrossLinks(r, view, repo),
113 65 }
114 65 a.render(w, http.StatusOK, pageName(view.Template()), envelope)
115 }
116
117 // headCommit reads the head commit of ref for the envelope's freshness line: a
118 // board rendered from a store that stopped receiving pushes yesterday is
119 // otherwise indistinguishable from a current one.
120 //
121 // It returns nil rather than an error, and that is the whole contract. A
122 // database with no commits, or a Log that fails, must still render the view —
123 // this is decoration on top of an answer, and it may never be the reason a
124 // reader gets a 500 instead of a board. The partial renders nothing for nil.
125 68 func headCommit(ctx context.Context, sess BrowseSession, ref string) *browse.CommitInfo {
126 68 commits, _, err := sess.Log(ctx, ref, "", 1)
127 68 if err != nil || len(commits) == 0 {
128 55 return nil
129 55 }
130 13 return &commits[0]
131 }