~bigbes/sr-ht-dolt

ref: 377c616a8a3b385f18a8d6509c7a6be3a31af2bd sr-ht-dolt/web/handlers_view.go -rw-r--r-- 4.3 KiB
377c616a — Eugene Blikh web: render memory bodies as markdown, and resolve their references 3 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
package web

import (
	"context"
	"errors"
	"net/http"

	"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"
)

// handleView renders a specialized alternative view of a repository. The view
// is selected by the {view} URL slug and must both exist in a.views and Apply
// to the tables at the resolved ref; otherwise the request is a 404 (the view
// simply does not exist for this repo). The generic table browser remains
// reachable via the tree/table routes regardless.
//
// The ref is taken from ?ref= (default: the repo's default branch). The view's
// own Template() is rendered with a fixed envelope: the chrome, the repo, the
// ref and branch list, the applicable view tabs, and the opaque value the view
// produced from Build (as .Data).
func (a *app) handleView(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 := r.URL.Query().Get("ref")
	if ref == "" {
		branches, err := sess.Branches(r.Context())
		if err != nil {
			http.Error(w, "failed to list branches", http.StatusInternalServerError)
			return
		}
		ref = browse.DefaultBranch(branches)
	}

	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
	}

	// Find the requested view; an unknown slug or a view that does not fingerprint
	// these tables is reported as not found — the view does not exist here.
	slug := chi.URLParam(r, "view")
	var view View
	for _, v := range a.views {
		if v.Name() == slug {
			view = v
			break
		}
	}
	if view == nil || !view.Applies(tables) {
		a.notFound(w, r)
		return
	}

	data, err := view.Build(r.Context(), sess, repo, ref, r.URL.Query())
	if err != nil {
		http.Error(w, "failed to build view", http.StatusInternalServerError)
		return
	}

	// Re-list branches for the chrome/tab bar. Cheap and keeps the ref selector
	// consistent whether or not ?ref= was supplied.
	branches, _ := sess.Branches(r.Context())

	envelope := struct {
		chrome.Page
		Repo     *core.Repo
		Ref      string
		Branches []browse.Branch
		Views    []View
		Head     *browse.CommitInfo
		Data     any
		// Links renders the issue ids inside stored prose as links to the database
		// that owns them. It is built by the one rendering that needs it — the
		// beads detail pane — and is nil for every other page here, because
		// building it opens a store per database the caller may browse. A nil one
		// still renders text; see beadLinks.Text.
		Links *beadLinks
		// Memories renders a memory's stored markdown, with its [[slug]]
		// references and the issue ids in its prose linked to the databases that
		// hold them. It is built by the memory view alone, for the reason Links is
		// built by the detail pane alone: it opens a store per database this
		// caller may browse. A nil one still renders the markdown, with every
		// reference left as text; see memoryLinks.Body.
		Memories *memoryLinks
	}{
		Page:     a.page(r, view.Label()+" — "+repo.OwnerName+"/"+repo.Name),
		Repo:     repo,
		Ref:      ref,
		Branches: branches,
		Views:    applicableViews(a.views, tables),
		Head:     headCommit(r.Context(), sess, ref),
		Data:     data,
		Links:    a.beadCrossLinks(r, view, repo, data),
		Memories: a.memoryCrossLinks(r, view, repo),
	}
	a.render(w, http.StatusOK, pageName(view.Template()), envelope)
}

// headCommit reads the head commit of ref for the envelope's freshness line: a
// board rendered from a store that stopped receiving pushes yesterday is
// otherwise indistinguishable from a current one.
//
// It returns nil rather than an error, and that is the whole contract. A
// database with no commits, or a Log that fails, must still render the view —
// this is decoration on top of an answer, and it may never be the reason a
// reader gets a 500 instead of a board. The partial renders nothing for nil.
func headCommit(ctx context.Context, sess BrowseSession, ref string) *browse.CommitInfo {
	commits, _, err := sess.Log(ctx, ref, "", 1)
	if err != nil || len(commits) == 0 {
		return nil
	}
	return &commits[0]
}